<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hk_98.log</title>
        <link>https://velog.io/</link>
        <description>꾸준하게 가자</description>
        <lastBuildDate>Tue, 24 Jun 2025 11:01:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hk_98.log</title>
            <url>https://velog.velcdn.com/images/hk_98/profile/07510986-c4b1-4b09-9273-1a964b3d08e4/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hk_98.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hk_98" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[인터페이스의 메소드]]></title>
            <link>https://velog.io/@hk_98/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%A9%94%EC%86%8C%EB%93%9C</link>
            <guid>https://velog.io/@hk_98/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%A9%94%EC%86%8C%EB%93%9C</guid>
            <pubDate>Tue, 24 Jun 2025 11:01:03 GMT</pubDate>
            <description><![CDATA[<br />
<br />

<p>인터페이스를 사용하면서도 많은 문제가 있다. 그런 것들을 해결해주기 위해 Java8 이후에는 <code>default</code>, <code>static</code>, <code>private</code> 메소드가 나오게 되었다.</p>
<p>만약 인터페이스를 모르고 있다면 필자가 작성한 인터페이스 글을 보고 오는 것을 추천한다.</p>
<br />

<h2 id="🧩-default-메소드">🧩 default 메소드</h2>
<p><strong>default 메소드</strong>는 어떻게 나오게 되었을까? 만약 <code>A</code>라는 인터페이스를 구현하는 클래스가 <code>A1</code>, <code>A2</code>, <code>A3</code> 총 3개가 있다고 가정해보자. 그런데 기능 추가를 하고 싶어 인터페이스에 <code>methodA</code> 라는 것을 추가해야한다. 추가하는 방법은 구현 클래스 <code>A1</code>, <code>A2</code>, <code>A3</code>에 오버라이드를 통해 <code>methodA</code>를 추가하면 된다. </p>
<p>하지만 <code>A</code> 인터페이스를 구현하는 클래스가 약 100개쯤 된다면, <code>methodA</code>를 추가하기 위해서 모든 구현 클래스에 직접 구현하는 것은 너무 비효율적일 것이다. 이런 문제를 해결하기 위해 Java 8에서 도입된 것이 바로 <strong>default 메소드</strong>이다.</p>
<p>이를 코드로 구현한다면 아래와 같다.</p>
<pre><code class="language-java">public interface A {

    public abstract void method1();

    // 추가한 메소드
    default void method2() {
        System.out.println(&quot;추가한 기능&quot;);
    }
}

public class A1 implements A {

    @Override
    public void method1() {
        System.out.println(&quot;메소드1 호출&quot;);
    }
}

public class A2 implements A {

    @Override
    public void method1() {
        System.out.println(&quot;메소드1 호출&quot;);
    }
}

public class Example {

    public static void main(String[] args) {
        A a1 = new A1();
        A a2 = new A2();

        a1.method1(); // 메소드1 호출
        a2.method1(); // 메소드1 호출

        a1.method2(); // 추가한 기능
        a2.method2(); // 추가한 기능
    }
}
</code></pre>
<p>위의 코드를 보면 인터페이스 <code>A</code>가 있고 이를 구현하는 클래스인 <code>A1</code>과 <code>A2</code>가 있다. 인터페이스에는 추상 메소드인 <code>method1()</code>이 있고 <code>method2()</code>라는 <strong>default 메소드</strong>가 있다. 그리고 구현 클래스에서 인터페이스의 <code>method1()</code>을 구현할 때에는 <code>A1</code>과 <code>A2</code>에 오버라이드를 통해 직접 구현을 해주었다. </p>
<p>하지만 <code>method2()</code>를 추가할 때에는 <strong>default 메소드</strong>를 통해 굳이 직접 오버라이드를 통해 구현 클래스에 구현해주지 않아도 알아서 추가 되는 것을 위의 코드를 통해서 확인할 수 있다.</p>
<p><strong>default 메소드</strong>의 선언 방법은 추상 메소드의 <code>public abstract</code> 자리에 <code>default</code> 키워드를 사용하고 구현부를 작성해주면 된다.</p>
<br />

<h3 id="⚔️-default-메소드의-다중-상속-문제">⚔️ default 메소드의 다중 상속 문제</h3>
<p>반면 <strong>default 메소드</strong>에서는 다중 상속 문제가 있는데 한 번 알아보자.</p>
<br />

<h4 id="1-다중-인터페이스들-간의-default-메소드-충돌">1. 다중 인터페이스들 간의 default 메소드 충돌</h4>
<p>첫번째로 다중 인터페이스 간의 <strong>default 메소드</strong> 충돌이다. 이것은 하나의 구현 클래스에서 여러 개의 인터페이스를 구현할 수 있어서 발생하는 문제이다. 만약 인터페이스 <code>A</code>와 <code>B</code>에서 같은 형태의 default 메소드를 구현을 하게 된다면 <code>A</code>와 <code>B</code>의 default 메소드 중 어느 것을 실행해야할지 모호해진다. 그래서 이를 조치해주지 않으면 아래의 코드에서처럼 컴파일 자체가 되지 않는다.</p>
<pre><code class="language-java">public interface A {

    public abstract void method1();

    // 추가한 메소드
    default void method2() {
        System.out.println(&quot;A의 디폴트 메소드&quot;);
    }
}

public interface B {
    // 추가한 메소드
    default void method2() {
        System.out.println(&quot;B의 디폴트 메소드&quot;);
    }
}

public class A1 implements A, B {

    @Override
    public void method1() {
        System.out.println(&quot;A1 : 메소드1 호출&quot;);
    }
}

public class Example {

    public static void main(String[] args) {
        A a = new A1();
        a.method2(); // java: types interfaceMethod.A and interfaceMethod.B are incompatible
    }
}</code></pre>
<p>그렇다면 이 문제에 대한 해결 방법은 무엇일까? </p>
<p>그것은 오버라이딩을 하면 된다. 인터페이스를 구현한 클래스에서 디폴트 메소드를 커스터마이징 하거나 <code>super</code>를 통해 직접적으로 메소드를 호출할 인터페이스를 명시하는 방법이 있다. 인터페이스의 <code>super</code>는 뒤에서 설명하도록 하겠다.</p>
<pre><code class="language-java">public class A1 implements A, B  {

    @Override
    public void method1() {
        System.out.println(&quot;A1 : 메소드1 호출&quot;);
    }

    @Override
    public void method2() {
        // 커스터마이징
        System.out.println(&quot;커스터마이징&quot;);

       // super를 통해 원하는 인터페이스의 default 메서드를 명시적으로 호출
       A.super.method2();
    }
}</code></pre>
<br />

<h4 id="2-인터페이스의-디폴트-메소드와-부모-클래스-메소드-간의-충돌">2. 인터페이스의 디폴트 메소드와 부모 클래스 메소드 간의 충돌</h4>
<p>두번째로는 인터페이스를 구현하면서 추상 클래스를 상속받는 경우에 메소드 간의 충돌이 발생하는 문제이다. </p>
<p>우선 코드를 한 번 보자.</p>
<pre><code class="language-java">public interface A {

    public abstract void method1();

    // 추가한 메소드
    default void method2() {
        System.out.println(&quot;A의 디폴트 메소드&quot;);
    }
}

public abstract class C {

    public void method2() {
        System.out.println(&quot;C의 인스턴스 메소드&quot;);
    }
}

public class A1 extends C implements A  {

    @Override
    public void method1() {
        System.out.println(&quot;A1 : 메소드1 호출&quot;);
    }
}

public class Example {

    public static void main(String[] args) {
        A a = new A1();
        C c = new A1();
        A1 a1 = new A1();

        a.method2(); // C의 인스턴스 메소드
        c.method2(); // C의 인스턴스 메소드
        a1.method2(); // C의 인스턴스 메소드
    }
}</code></pre>
<p>코드에서처럼 <code>A1</code> 클래스에서 <code>A</code> 인터페이스를 구현하고 <code>C</code> 추상 클래스를 상속 받았을 때 <code>A</code>와 <code>C</code>로부터 같은 이름의 메소드를 상속 받을때, 인터페이스 타입, 추상 클래스 타입, 구현 클래스 타입으로 중복되는 메소드를 실행한다면 추상 클래스의 메소드가 실행이 된다는 것을 알 수 있다.</p>
<p>만약 인터페이스 쪽의 디폴트 메소드를 사용하고 싶다면 구현 클래스에서 오버라이딩 하여 사용하면 된다.</p>
<pre><code class="language-java">public class A1 extends C implements A  {

    @Override
    public void method1() {
        System.out.println(&quot;A1 : 메소드1 호출&quot;);
    }

    @Override
    public void method2() {
        System.out.println(&quot;A1 인터페이스의 디폴트 메소드&quot;);
    }
}</code></pre>
<p>따라서 인터페이스 간 default 메서드 충돌은 반드시 구현 클래스에서 오버라이딩을 통해 명시적으로 해결해야 하며 필요 시 <code>super</code>를 활용해 원하는 인터페이스의 동작을 선택할 수 있다.</p>
<br />

<h3 id="🧭-default-메소드의-super">🧭 default 메소드의 super</h3>
<p>앞서 여러 인터페이스에서 같은 default 메소드가 있을 때 메소드 충돌에 대한 해결책으로 <code>super</code>가 있다고 하였다. <code>super</code>는 원래 클래스 상속에서 부모 클래스의 멤버를 참조할 때 사용하는 키워드인데, 인터페이스의 default 메소드에서도 유사한 방식으로 활용할 수 있다.</p>
<p>이와 같이 인터페이스에서도 default 메소드를 구현한 클래스에서 오버라이딩 했을때, <code>super</code> 키워드를 통해 특정 인터페이스의 디폴트 메소드 호출이 가능해진다.</p>
<p>다만 클래스의 <code>super</code> 호출과는 약간의 차이가 있다. <code>인터페이스명.super.디폴트메소드</code>의 방식으로 호출하면 된다.</p>
<p>즉, 클래스가 여러 인터페이스를 구현하는 경우에는 <code>super</code> 앞에 인터페이스명을 반드시 명시해서 어떤 default 메소드를 호출할지 컴파일러가 판단할 수 있어서 default 메소드 충돌을 방지할 수 있다는 것이다.</p>
<pre><code class="language-java">public interface A {

    public abstract void method1();

    // 추가한 메소드
    default void method2() {
        System.out.println(&quot;A의 디폴트 메소드&quot;);
    }
}

public interface B {
    // 추가한 메소드
    default void method2() {
        System.out.println(&quot;B의 디폴트 메소드&quot;);
    }
}

public class A1 implements A, B  {

    @Override
    public void method1() {
        System.out.println(&quot;A1 : 메소드1 호출&quot;);
    }

    @Override
    public void method2() {
        A.super.method2();
        B.super.method2();
    }
}

public class Example {

    public static void main(String[] args) {
        A a = new A1();
        a.method2();
    }
}</code></pre>
<pre><code>실행 결과

A의 디폴트 메소드
B의 디폴트 메소드</code></pre><p>이와 같은 코드를 작성하면서 만약 하나의 인터페이스만 구현하는 클래스에서 인터페이스의 default 메소드를 호출할 때 <code>super.디폴트메소드</code>로 호출이 가능한지 테스트 해보았다. </p>
<p>그런데 이와 같은 방식으로는 작동이 안되었고 IDE에서도 앞에 인터페이스명을 추가하라고 문구가 나오는 것을 확인했다. 그래서 아무리 구현하는 인터페이스가 하나라고 해도 맨앞에 인터페이스명을 붙여줘야 한다.</p>
<br />
<br />

<h2 id="⚙️-static-메소드">⚙️ static 메소드</h2>
<p>Java8부터는 인터페이스에서 <strong>static 메소드</strong>를 선언할 수 있게 되었다. 이전에는 static 메소드를 인터페이스에 허용하지 않았는데, 이는 인터페이스의 역할을 모호하게 만들 수 있기 때문이었다.</p>
<p>인터페이스는 객체가 가져야 할 <strong>행위(기능)</strong>를 정의하는 설계도와 같은 역할을 한다. 하지만 static 메소드는 인스턴스 없이 호출되며, 이미 완전한 구현체를 가진 정적 메소드이기 때문에 이러한 메소드가 인터페이스에 포함되면 인터페이스의 단일 책임 원칙에 어긋날 수 있다는 우려가 있었다.</p>
<p>하지만 그럼에도 Java8에서는 <strong>static 메소드</strong>가 도입이 되었다. static 메소드는 default 메소드와 마찬가지로, 기존 인터페이스에 새로운 메소드를 추가할 때 모든 구현 클래스에서 이를 구현하지 않아도 되도록 해준다. 또한 static 메소드는 구현 클래스와 무관하게, 인터페이스 자체에 대한 공통 동작을 정의할 수 있다.</p>
<p>사용 방법으로는 <strong>인터페이스 명</strong>으로 접근해 사용할 수 있다.</p>
<pre><code class="language-java">public interface A {

    public abstract void method1();

    // 추가한 메소드
    default void method2() {
        System.out.println(&quot;A의 디폴트 메소드&quot;);
    }

    // static 메소드
    public static void method3() {
        System.out.println(&quot;A의 스태틱 메소드&quot;);
    }
}

public class A1 implements A, B  {

    @Override
    public void method1() {
        System.out.println(&quot;A1 : 메소드1 호출&quot;);
    }

    @Override
    public void method2() {
        A.super.method2();
    }
}

public class Example {

    public static void main(String[] args) {
        A a = new A1();
        a.method1();
        a.method2();
        // 스태틱 메소드
        A.method3(); 
    }
}</code></pre>
<pre><code>실행 결과

A1 : 메소드1 호출
A의 디폴트 메소드
A의 스태틱 메소드</code></pre><p>위 코드에서 <code>method1()</code>과 <code>method2()</code>는 인터페이스 타입의 변수로 메소드를 호출했지만, static 메소드인 <code>method3()</code>은 인터페이스명으로 직접 호출한 것을 볼 수 있다.</p>
<br />

<p>이러한 <strong>static 메소드</strong>는 유틸리티 메소드 제공 측면에서 인터페이스와 관련된 공통 작업 수행에 활용할 수 있다. 예를 들어, Java 코드를 작성하면서 수학 관련 기능을 활용하고자 할 때 <code>Math</code> 클래스와 관련된 유틸리티 메소드를 사용한 적이 있을 것이다. <strong>유틸리티 메소드란</strong>, 객체의 상태나 동작과 직접적으로 관련되진 않지만 자주 사용하는 보조적인 기능을 수행하는 메소드를 말한다. </p>
<p>물론 이 예시는 <code>Math</code> <strong>클래스</strong>를 기반으로 하고 있지만, static 메소드라는 점에서는 인터페이스에서도 동일하게 적용될 수 있으므로 적절한 예시가 될 수 있다.</p>
<p><code>Math</code>의 내부 코드를 한 번 보자.</p>
<pre><code class="language-java">public final class Math {

    private Math() {}

    public static final double E = 2.718281828459045;
    public static final double PI = 3.141592653589793;


    @IntrinsicCandidate
    public static int max(int a, int b) {
        return (a &gt;= b) ? a : b;
    }

    @IntrinsicCandidate
    public static int min(int a, int b) {
        return (a &lt;= b) ? a : b;
    }
}</code></pre>
<p>내부에서는 이런식으로 여러 개의 static 메소드가 있어서 우리가 수학 관련 메소드를 사용할 때 <code>Math.max()</code>, <code>Math.min()</code>과 같이 유틸리티 메소드를 사용할 수 있는 것이다.</p>
<p>이처럼 <code>Math</code> 클래스는 다양한 static 메소드를 통해 수학적 기능을 제공하며, 인터페이스의 static 메소드도 유사한 방식으로 사용될 수 있다.</p>
<br />
<br />

<h2 id="🔒-private-메소드">🔒 private 메소드</h2>
<p><strong>private 메소드</strong>는 Java9 버전에 추가된 메소드이다. 인터페이스에 default, static 메소드가 생긴 후 이러한 메소드들의 <strong>로직을 공통화하고 재사용</strong>하기 위해 생긴 메소드이다. 이러한 private 메소드도 구현부를 가져야 한다.</p>
<p>로직을 공통화하고 재사용 한다는 것은 예를 들어, 기존에는 default 메소드 내에 동일한 로직을 여러번 작성을 해야했다면 이제는 private 메소드를 통해 재사용성을 높일 수 있다는 것이다.</p>
<p>접근제어자를 알고 있다면 private 메소드에 대해 이해하기 쉬울 것이다. 접근제어자로 private을 클래스의 멤버에 활용한 적이 있다면 외부에서 접근을 할 수 없고 내부에서만 접근할 수 있는 접근제어자라는 것을 알고 있을 것이다. </p>
<p>이는 인터페이스에서도 같다. 인터페이스의 외부에서 private 메소드를 호출할 수 없기 때문에 private 메소드를 외부에서 호출하고자 한다면 default 메소드의 내부에서 간접적으로 호출하여 외부에서 default 메소드를 호출하면 된다. </p>
<p>선언 방법으로는 메소드를 선언할 때 제일 처음으로 <code>private</code>를 선언하고 메소드 내부를 구현하면 된다.</p>
<p>또한 <code>private static</code> 키워드를 붙인 메소드는 <strong>static 메소드</strong>에서만 호출이 가능하다.</p>
<p>코드를 통해 한 번 알아보자.</p>
<pre><code class="language-java">public interface A {
    // default 메소드
    default void method1() {
        method2();
    }

    // private 메소드
    private void method2() {
        System.out.println(&quot;A의 private 메소드&quot;);
    }

    // static 메소드
    static void method3() {
        method4();
    }

    // private static 메소드
    private static void method4() {
        System.out.println(&quot;A의 private static 메소드&quot;);
    }
}

public class Example {

    public static void main(String[] args) {
        A a = new A1();
        a.method1(); // A의 private 메소드
        A.method3(); // A의 private static 메소드
    }
}</code></pre>
<p>위의 코드를 보면 <code>private</code> 메소드를 실행하기 위해 default 메소드 내부에서 간접적으로 <code>private</code> 메소드를 실행하는 코드를 작성하였고 <code>private static</code> 메소드도 역시 간접적으로 실행하기 위해서 static 메소드 내부에서 간접적으로 실행하였다. </p>
<p>즉, <code>private</code> 메소드는 이러한 간접적인 방식으로 실행하면 된다.</p>
<br />
<br />
<br />

<blockquote>
<p><strong>참조</strong>
<a href="https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4Interface%EC%9D%98-%EC%A0%95%EC%84%9D-%ED%83%84%ED%83%84%ED%95%98%EA%B2%8C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC">https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4Interface%EC%9D%98-%EC%A0%95%EC%84%9D-%ED%83%84%ED%83%84%ED%95%98%EA%B2%8C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</a>
<a href="https://velog.io/@heoseungyeon/%EB%94%94%ED%8F%B4%ED%8A%B8-%EB%A9%94%EC%84%9C%EB%93%9CDefault-Method">https://velog.io/@heoseungyeon/%EB%94%94%ED%8F%B4%ED%8A%B8-%EB%A9%94%EC%84%9C%EB%93%9CDefault-Method</a>
<a href="https://lifework-archive-reservoir.tistory.com/282">https://lifework-archive-reservoir.tistory.com/282</a>
<a href="https://swlin23.tistory.com/32">https://swlin23.tistory.com/32</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[인터페이스 (Interface)]]></title>
            <link>https://velog.io/@hk_98/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-Interface</link>
            <guid>https://velog.io/@hk_98/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-Interface</guid>
            <pubDate>Mon, 23 Jun 2025 07:09:31 GMT</pubDate>
            <description><![CDATA[<br />
<br />

<h2 id="🧐-인터페이스란">🧐 인터페이스란?</h2>
<p>인터페이스란, 일종의 추상적인 설계도로, 추상 클래스와 유사하지만 더 엄격하고 높은 수준의 추상화를 제공한다. 추상 클래스처럼 추상 메서드를 포함할 수 있지만, <strong>일반 메서드(단, default, static, private 메서드는 가능)</strong>나 <strong>멤버 변수(상수 제외)</strong>는 포함할 수 없다.</p>
<p>좀 더 쉽게 설명하자면 우리는 실생활에서 인터페이스를 사용한 적이 있을 것이다. 대표적으로 규격화 되어 있는 <strong>리모컨</strong>이 있다. 리모컨은 TV 제조사가 다르더라도 리모컨이 동일한 버튼과 기능을 제공한다면, 그 리모컨은 여러 브랜드의 TV를 똑같은 방식으로 조작할 수 있다. </p>
<p>이처럼 인터페이스도 추상화를 통해 구현해야할 부분을 규격화하고 구현은 각 클래스가 담당하는 원리로 이루어져 있다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/f3b9f692-ae44-48b3-8aa3-ed3594ccad6f/image.png" width="60%"/></p>


<br />
<br />

<h2 id="📘-인터페이스-선언">📘 인터페이스 선언</h2>
<p>인터페이스의 선언은 클래스의 선언 자리에 <code>class</code> 대신에 <code>interface</code> 키워드를 사용하면 된다.</p>
<pre><code class="language-java">public interface Button {
    ...
}</code></pre>
<p>인터페이스 이름은 관례적으로 클래스 이름 작성 방법과 동일하다. 영어 대소문자로 구분하며, 첫 글자를 대문자로 하고 나머지는 소문자로 작성하는 것이 관례이다. 이름이 길어 여러 단어로 구성될 경우, 각 단어의 첫 글자를 대문자로 작성하는 <strong>파스칼 표기법(PascalCase)</strong>을 사용한다.</p>
<p>클래스는 멤버 변수와 메소드를 가지고 있는 반면에 인터페이스는 상수 필드와 추상 메소드를 구성 멤버로 가진다. 또한 인터페이스는 객체를 생성할 수 없기 때문에 생성자를 가지지 않는다.</p>
<pre><code class="language-java">// 버튼 인터페이스
public interface Button {

    // 상수 선언
    public static final int DEFAULT_WIDTH = 100;
    public static final int DEFAULT_HEIGHT = 50;

    // 추상 메소드 선언
    public abstract void onClick();
}</code></pre>
<p>인터페이스는 객체의 사용 방법(기능)을 정의하는 역할을 하므로 인스턴스 필드나 정적 필드를 선언할 수 없다. 하지만 <strong>상수</strong> 필드는 선언이 가능하다. 단, 상수는 고정된 값이기에 실행 시 데이터를 바꿀 수 없다.</p>
<p>선언 방법은 일반적인 상수 선언 방법과 같다. <code>public static final</code>을 변수 앞에 붙여서 선언할 수 있다. 그리고 인터페이스에서는 이를 생략하더라도 컴파일 과정에서 자동으로 붙여준다.</p>
<p><strong>추상메소드</strong>의 경우는 일반적인 추상 클래스에서와 똑같이 선언하면된다. 즉, <code>public abstract</code> 키워드를 메소드에 붙여주면 되는데 상수에서의 경우와 마찬가지로 이를 생략하여도 컴파일 과정에서 자동으로 붙게 된다.</p>
<br />
<br />

<h2 id="👨💻-인터페이스-구현">👨‍💻 인터페이스 구현</h2>
<p>처음에 인터페이스를 간단하게 설명하면서 인터페이스로 규격화된 기능을 설정하고 이를 클래스를 통해서 인터페이스를 구현해야한다고 하였다. 그렇다면 어떻게 구현할 수 있을까?</p>
<p>우선 간단한 예시를 보자.</p>
<pre><code class="language-java">// 버튼 인터페이스
public interface Button {

    // 상수 선언
    public static final int DEFAULT_WIDTH = 100;
    public static final int DEFAULT_HEIGHT = 50;

    // 추상 메소드 선언
    public abstract void onClick();
}

// 로그인 버튼 - 구현 클래스
public class LoginButton implements Button {

    @Override
    public void onClick() {
        System.out.println(&quot;로그인 화면으로 이동합니다.&quot;);
    }
}

// 종료 버튼 - 구현 클래스
public class ExitButton implements Button {

    @Override
    public void onClick() {
        System.out.println(&quot;프로그램을 종료합니다.&quot;);
    }
}</code></pre>
<p>위의 코드처럼 일반적인 클래스에 인터페이스를 구현할 때는 <code>implements</code>라는 키워드를 클래스 이름 뒤에 선언한 후 <code>implements</code> 뒤에 구현할 인터페이스를 명시해주면 된다. 그리고 클래스 안에 구현부를 작성해주면 끝이다. 또한 구현 클래스라고 해서 다른 것이 아닌 일반 클래스처럼 내부에서 멤버 변수 및 멤버 메소드를 작성할 수 있다.</p>
<p>그리고 인터페이스에 정의된 추상 메소드는 반드시 <strong>오버라이딩(재정의)</strong>하여 구현해야 한다.
이는 추상 클래스에서 추상 메소드를 구현할 때와 동일한 방식이다.</p>
<p>이로써 각 클래스는 인터페이스가 정의한 기능의 규약을 따르게 된다.</p>
<br />

<h3 id="🎛️-다중-인터페이스-구현-클래스">🎛️ 다중 인터페이스 구현 클래스</h3>
<p>또한 자바에서는 클래스는 하나만 상속할 수 있지만 인터페이스는 여러 개를 동시에 구현할 수 있다.</p>
<pre><code class="language-java">
public interface Button {
    public abstract void onClick();

    // 공통된 메소드
    public abstract void delete();
}

public interface Toggleable {
    public abstract void toggle();

    // 공통된 메소드
    public abstract void delete();
}

// 다중 인터페이스 구현 클래스
public class ToggleButton implements Button, Toggleable {

    boolean isToggle = false;

    @Override
    public void onClick() {
        isToggle = !isToggle;
        toggle();
    }

    @Override
    public void toggle() {
        if (isToggle) {
            System.out.println(&quot;토글에 의해 화면이 띄워짐&quot;);
        } else {
            System.out.println(&quot;토글에 의해 화면이 없어짐&quot;);
        }
    }

    @Override
    public void delete() {
        System.out.println(&quot;삭제!&quot;);
    }
}


public class Example {
    public static void main(String[] args) {
        ToggleButton toggleButton = new ToggleButton();

        toggleButton.onClick(); // 토글에 의해 화면이 띄워짐
        toggleButton.onClick(); // 토글에 의해 화면이 없어짐
        toggleButton.toggle(); // 토글에 의해 화면이 없어짐

        Button button = toggleButton;

        button.onClick(); // 토글에 의해 화면이 띄워짐
        button.onClick(); // 토글에 의해 화면이 없어짐

        Toggleable toggleable = toggleButton;
        toggleable.toggle(); // 토글에 의해 화면이 없어짐

        toggleButton.delete(); // 삭제!
    }
}
</code></pre>
<p>위의 코드의 <code>ToggleButton</code> 클래스처럼 <code>implements</code> 키워드 뒤에 기능을 구현하고자 하는 인터페이스들을 작성해주면 다중 인터페이스를 구현할 수 있게 된다. 여러 개의 인터페이스를 구현하는 경우, 각 인터페이스에 선언된 모든 추상 메서드를 구현해야 한다. </p>
<p>실행 코드를 보면 먼저 일반 클래스인 <code>ToggleButton</code> 타입변수로 받은 다음 실행해보았다. <code>ToggleButton</code> 클래스에는 <code>onClick()</code>과 <code>toggle()</code> 메소드가 둘 다 구현이 되어 있어 실행이 가능하다. </p>
<p>반면 다음 코드에서는 <code>Button</code> 또는 <code>Toggleable</code> 인터페이스 타입으로 객체를 참조할 경우, 참조한 타입에 선언된 메서드만 호출할 수 있다.</p>
<p>이는 <strong>다형성(polymorphism)</strong>의 개념을 기반으로 하며, 구현 객체는 모든 기능을 가지고 있어도 참조 타입에 따라 접근 가능한 범위가 달라진다. </p>
<p>하지만 코드를 작성하면서 <code>onClick()</code>을 실행하면서 <code>toggle()</code>을 내부에서 간접적으로 실행해 놓았기 때문에 <code>toggle()</code>로 실행하여 결과가 달라지지 않는 것과는 달리 <code>Button</code> 타입으로 <code>onClick()</code>을 실행하면 토글 기능이 잘 작동한다는 것을 알 수 있다.</p>
<p>즉, 이 코드에서는 다중 인터페이스를 통해 버튼에 토글 기능을 추가하여 구현한 것이기에 여러 기능을 독립적으로 분리하고 조합할 수 있다는 장점이 있다.</p>
<p>이러한 구조는 <strong>SRP(단일 책임 원칙)</strong>을 지키면서도, 유연하고 확장 가능한 설계를 가능하게 해준다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/a3703272-21e2-465e-a9aa-4fd681568aea/image.png" width="60%"/></p>

<br />

<p>또한 인터페이스는 다중 상속이 되기 때문에 같은 시그니처 형태의 메소드를 여러 인터페이스에서 공통으로 상속해주고 이를 구현하는 것도 가능하다.</p>
<p>코드에서 보면 <code>Button</code> 인터페이스와 <code>Toggleable</code> 인터페이스에 같은 시그니처 형태로 <code>delete()</code> 메소드를 정의하였고 이를 <code>ToggleButton</code> 구현 클래스에 재정의를 한 후 실행했더니 잘 작동되었다. </p>
<p>이는 Java에서 클래스의 경우 여러 상위 클래스에서 같은 시그니처 형태의 메소드를 상속 받아 실행하려고 할 때 어떤 상위 클래스의 메소드를 사용할지 모호성이 발생한다. 이를 <strong>다이아몬드 문제</strong>라고 한다. </p>
<p>하지만 인터페이스의 경우 메소드는 정의만 하고 실제 구현은 구현 클래스에서 담당하기 때문에 메소드의 형태가 겹쳐도 상관이 없다.</p>
<p>이처럼 자바에서는 클래스는 하나만 상속받을 수 있지만 인터페이스는 여러 개를 동시에 구현할 수 있다. 이를 통해 기능을 모듈화하고 재사용성 높은 코드 구조를 만들 수 있으며 중복된 시그니처의 메서드도 충돌 없이 구현할 수 있다.</p>
<br />
<br />

<h2 id="🚀-인터페이스-사용">🚀 인터페이스 사용</h2>
<p>그렇다면 이렇게 인터페이스를 정의하고 구현 클래스를 구현했다면 실행코드에서는 어떤식으로 실행 되는 것일까?</p>
<p>예제로 확인해보자.</p>
<pre><code class="language-java">// 인터페이스
public interface Button {

    public static final int DEFAULT_WIDTH = 100;
    public static final int DEFAULT_HEIGHT = 50;

    public abstract void onClick();
}

// 로그인 버튼 - 구현 클래스
public class LoginButton implements Button {

    private int loginAttemptCount = 0;

    @Override
    public void onClick() {
        memberMethod1(); // 간접 사용
        this.loginAttemptCount++;
        System.out.println(&quot;로그인 화면으로 이동합니다. (시도 횟수: &quot; + loginAttemptCount + &quot;)&quot;);
    }

    public void memberMethod1() {
        System.out.println(&quot;멤버 메소드 간접적으로 사용&quot;);
    }

}

// 종료 버튼 - 구현 클래스
public class ExitButton implements Button {

    private boolean isRealExit = false;

    @Override
    public void onClick() {

        if (!isRealExit) {
            isRealExit = true;
            System.out.println(&quot;정말 종료하시겠습니까? 한 번 더 눌러주세요.&quot;);
        } else {
            isRealExit = false;
            System.out.println(&quot;프로그램을 종료합니다.&quot;);
        }

    }
}

// 실행 코드
public class Example {
    public static void main(String[] args) {
        Button loginButton = new LoginButton();
        Button exitButton = new ExitButton();

        // 로그인 버튼 클릭 2번
        loginButton.onClick(); // 시도 횟수 : 1
        loginButton.onClick(); // 시도 횟수 : 2


        // 종료 버튼 클릭 2번
        exitButton.onClick(); // 정말 종료하시겠습니까? 한 번 더 눌러주세요.
        exitButton.onClick(); // 프로그램을 종료합니다.

        // 상수 출력
        System.out.println(Button.DEFAULT_WIDTH); // 100
        System.out.println(Button.DEFAULT_HEIGHT); // 50
    }
}</code></pre>
<pre><code>실행 결과

멤버 메소드 간접적으로 사용
로그인 화면으로 이동합니다. (시도 횟수: 1)
멤버 메소드 간접적으로 사용
로그인 화면으로 이동합니다. (시도 횟수: 2)
정말 종료하시겠습니까? 한 번 더 눌러주세요.
프로그램을 종료합니다.
100
50</code></pre><p>사용방법으로 우선 인터페이스와 구현 클래스를 작성한 후, 실행 코드에서는 인터페이스 타입의 변수에 구현 클래스의 인스턴스를 할당하여 사용할 수 있다. 이렇게 하면 인터페이스 타입으로 구현 클래스의 객체를 다룰 수 있다. 그리고 이렇게 객체를 할당받은 인터페이스 변수를 통해서 메소드를 실행할 수 있다. </p>
<p>또한 앞서 인터페이스에 정의된 상수는 <code>public static final</code>로 선언된다고 하였다. 즉, 클래스의 공용 상수처럼 작동하기 때문에 상수에 접근하기 위해서는 객체가 아닌 인터페이스 이름으로 접근해야 한다. 코드의 <code>Button.DEFAULT_WIDTH</code>처럼 사용하는 것이 대표적인 예다. 반면 클래스의 static 변수나 상수처럼 인스턴스 상수를 인스턴스를 통해 접근하려 하면 경고가 발생할 수 있고 Java에서는 권장하지 않는 방식이다.</p>
<p>다만, 여기서 주의할 점이 있다. 예제를 작성하면서 구현 클래스에 멤버 변수와 멤버 메소드를 직접적으로 사용할 수 있는지 궁금해서 작성해 보았지만 사용할 수 없었다. 그 이유는 인터페이스에서는 정의되지 않은 <strong>구현 클래스의 멤버(변수, 메서드)</strong>에 접근하려고 했기 때문이다.</p>
<p>그래서 코드를 보면 <code>LoginButton</code>에서 멤버 메소드를 사용하기 위해서 인터페이스의 추상 메소드를 구현한 메소드에서 간접적으로 호출하여 사용하였다. 또 다른 방법으로는 인터페이스 타입을 구현 클래스 타입으로 캐스팅하는 방법을 사용할 수 있지만, 구현 클래스에 의존하게 되므로 다형성을 제대로 활용하지 못하게 된다.</p>
<br />
<br />
<br />

<blockquote>
<p>참조
<a href="https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4Interface%EC%9D%98-%EC%A0%95%EC%84%9D-%ED%83%84%ED%83%84%ED%95%98%EA%B2%8C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC">https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4Interface%EC%9D%98-%EC%A0%95%EC%84%9D-%ED%83%84%ED%83%84%ED%95%98%EA%B2%8C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</a>
<a href="https://dkswnkk.tistory.com/459">https://dkswnkk.tistory.com/459</a>
혼자 공부하는 자바 - 신용권(한빛 미디어)</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[다형성]]></title>
            <link>https://velog.io/@hk_98/%EB%8B%A4%ED%98%95%EC%84%B1</link>
            <guid>https://velog.io/@hk_98/%EB%8B%A4%ED%98%95%EC%84%B1</guid>
            <pubDate>Tue, 17 Jun 2025 15:28:33 GMT</pubDate>
            <description><![CDATA[<br />
<br />

<h1 id="🔁-다형성이란">🔁 다형성이란?</h1>
<p>사용 방법은 동일하지만 다양한 객체를 이용해서 다양한 실행 결과가 나오도록 하는 성질이다. 다시 말하자면 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 수행할 수 있다는 것이다.</p>
<br />

<p>그러면 어떤 방식으로 자바에서는 다형성을 구현하는지 알아보자.</p>
<br />

<h2 id="🔄-자동-타입-변환">🔄 자동 타입 변환</h2>
<p>우선 다형성을 공부하기 위해서는 자동 타입 변환이라는 개념을 이해해야 한다. <strong>자동 타입 변환</strong>이란, 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다. 타입 변환이라고 하면 자바를 배우면서 기본 타입 변환에 대해서 배운 적이 있을 것이다. 이와 마찬가지로 클래스도 타입 변환을 할 수 있는데 그것은 상속 관계를 통해 타입 변환을 할 수 있다.</p>
<p>자동 타입 변환은 상속 관계에서 부모 타입 변수에 자식 타입을 할당하게 되면 발생한다.</p>
<pre><code class="language-java">public class Parent {
    ...
}

public class Child extends Parent {
    ...
}

public class Example {
    public static void main(String[] args) {

        Child child = new Child();
        Parent parent = child;

        System.out.println(child == parent); // true
    }
}

</code></pre>
<p>위의 코드에서처럼 자식 타입 변수 child에는 Child 객체의 주소 값이 할당되어 있고 이러한 주소 값을 부모 타입에 할당하면 자동 타입 변환이 일어나게 된다. 그래서 부모 타입 변수 parent에 Child 객체의 주소 값이 할당되게 되어 둘이 같은 주소 값을 가지게 된다는 것이다.</p>
<p>그림으로 보자면 아래와 같다.</p>
<p>
    <img src="https://velog.velcdn.com/images/hk_98/post/b80e77e9-a0cf-4ec0-a863-d8a559f30781/image.png" width="60%" />
</P>


<p>여기까지 왔으면 자동 타입 변환이라는 게 상속 관계에서 작동하는 것이라면 부모뿐만이 아닌 조상 클래스와의 관계에서도 가능한지 궁금할 것이다. 결론부터 말하자면 가능하다.</p>
<p>말로 설명하면 이해가 잘 안 갈 수 있으니 코드로 먼저 보여주겠다.</p>
<pre><code class="language-java">
public class GrandParent {
    ...    
}

public class Parent extends GrandParent {
    ...
}

public class OtherParent extends GrandParent {
    ...
}

public class Child extends Parent {
    ...
}

public class OtherChild extends OtherParent {
    ...
}

public class Example {
    public static void main(String[] args) {

        Parent parent = new Parent();
        OtherParent otherParent = new OtherParent();
        Child child = new Child();
        OtherChild otherChild = new OtherChild();

        GrandParent g1 = parent;
        GrandParent g2 = otherParent;
        GrandParent g3 = child;
        GrandParent g4 = otherChild;

        Parent parent2 = child;
        OtherParent otherParent2 = otherChild;

        // 상속 관계에 있지 않아서 컴파일 에러 발생
        // Parent parent3 = otherChild;
        // OtherParent otherParent3 = child;

    }
}</code></pre>
<p>이 코드에서는 GrandParent 클래스를 기준으로 두 갈래로 상속이 이루어진다.</p>
<p>
    <img src="https://velog.velcdn.com/images/hk_98/post/de0355b1-e734-4419-ac13-8367a821d0c6/image.png" width="45%" />
</P>


<p>GrandParent는 모든 클래스의 상위에 있기 때문에 아래에 상속받는 모든 클래스의 객체 주소 값을 GrandParent 타입의 변수에 할당할 수 있다. 또한 상속 관계에 있는 <code>Parent - Child</code>, <code>OtherParent - OtherChild</code>의 경우에도 자동 타입 변환이 가능하다. 하지만 <code>Parent - OtherChild</code>, <code>OtherParent - Child</code> 간의 관계는 상속 관계가 아니기 때문에 자동 타입 변환이 불가능하다.</p>
<br />

<p>그렇다면 이렇게 자동 타입 변환으로 부모 타입에 자식 객체를 할당할 경우에는 부모와 자식의 모든 필드와 메소드에 접근이 가능할까? 코드로 알아보자.</p>
<pre><code class="language-java">public class Parent {

    String name;
    int age;

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

    void method1() {
        System.out.println(&quot;부모 메소드1&quot;);
    }

    void method2() {
        System.out.println(&quot;부모 메소드2&quot;);
    }

    void method3() {
        System.out.println(&quot;부모 메소드3&quot;);
    }
}

public class Child extends Parent {

    String name;
    int age;
    int height;

    public Child(String parentName, int parentAge, String name, int age, int height) {
        super(parentName, parentAge);
        this.name = name;
        this.age = age;
        this.height = height;
    }


    @Override
    void method2() {
        System.out.println(&quot;자식 메소드2&quot;);
    }

    void method3() {
        System.out.println(&quot;자식 메소드3&quot;);
    }
}

public class Example {
    public static void main(String[] args) {

        Child child = new Child(&quot;철수 부모&quot;, 42, &quot;철수&quot;, 18, 178);

        Parent parent = child;

        System.out.println(parent.name); // 철수 부모
        System.out.println(parent.age); // 42
        // System.out.println(parent.height); 참조 불가능
        System.out.println(child.height); // 178

        parent.method1(); // 부모 메소드1
        parent.method2(); // 자식 메소드2
        parent.method3(); // 자식 메소드3
        // parent.method4(); 호출 불가능
    }
}</code></pre>
<p>코드로 작성해 보니, 자식 객체를 참조하고 있어도 자동 타입 변환을 통해 부모 타입의 변수로는 부모 클래스에 정의된 필드와 메소드에만 직접 접근할 수 있다. 즉, <strong>참조 변수의 타입이 접근 가능한 멤버의 범위를 결정</strong>한다.</p>
<p>하지만 여기서 한 가지 흥미로운 점은, <code>parent</code> 참조 변수로 <code>method2()</code>와 <code>method3()</code>를 호출했을 때 출력 결과는 <strong>자식 클래스의 메소드가 실행된다는 것</strong>이다. 이는 동적 바인딩 때문이다. </p>
<p><strong>동적 바인딩(Dynamic Binding)</strong>이란, 자바는 메소드 호출 시점에 참조 변수의 타입이 아니라 실제 객체의 타입을 기준으로 어떤 메소드를 실행할지 결정하는 데 이를 동적 바인딩이라고 한다. 동적 바인딩은 객체지향을 지원하는 언어에서는 대부분 지원하는 기능이다. </p>
<p>이어서 <code>method2()</code>의 경우, 자식 클래스에서 <code>@Override</code> 애너테이션을 사용하여 부모의 메소드를 재정의했고, 자바는 이처럼 오버라이딩된 메소드에 대해 <strong>동적 바인딩을 적용</strong>하므로, 실행 시점에 실제 객체가 자식 클래스일 경우 <strong>자식 클래스의 메소드가 호출된다</strong>.</p>
<p>또한 <code>method3()</code>도 마찬가지다. <code>@Override</code> 애너테이션은 생략되었지만, 자식 클래스에서 부모와 <strong>같은 시그니처(메소드 이름, 매개변수, 반환형)</strong>로 정의되었기 때문에 자동으로 오버라이딩 관계가 성립하고, <strong>동적 바인딩에 의해 자식 클래스의 <code>method3()</code>가 호출된다.</strong></p>
<p>여기서 메소드 재정의를 할 때 <code>@Override</code>를 사용해도 재정의가 되고 사용하지 않고 같은 시그니처로 정의하면 재정의가 가능하다는 것을 알게 되었다. </p>
<p>그러면 굳이 <code>@Override</code>를 사용하지 않아도 되지 않나라고 생각이 들 것이다. <code>@Override</code>를 사용해서 재정의를 하게 된다면 해당 메소드가 정확히 부모 클래스의 메소드를 재정의하고 있는지 컴파일러가 검사를 한다. 검사를 통해 개발자가 메소드를 재정의하면서 오타를 내어도 새로운 메소드로 인식하지 않고 오류를 발생시켜 개발자의 실수를 줄여준다. </p>
<p>또한 협업 시에는 수많은 메소드 중 어떤 것이 재정의된 메소드인지 바로 확인하는 것이 중요하다. <code>@Override</code>가 붙어 있으면 해당 메소드가 부모 클래스의 메소드를 오버라이드 한 것임을 명확히 알 수 있어 리팩토링이나 디버깅 시 큰 도움이 된다.</p>
<p>따라서 <code>@Override</code>는 단순한 문법 장식이 아니라, 코드의 안전성과 가독성을 높여주는 중요한 도구이기 때문에 사용하는 것이 좋다.</p>
<br />
<br />

<h2 id="🧪-필드의-다형성">🧪 필드의 다형성</h2>
<p>앞서 자동 타입 변환에 대해서 알아보았다. 자식 객체를 직접 사용해도 되는데, 왜 굳이 부모 타입으로 선언할까? 그것은 다형성을 구현하기 위해서이다. 필드의 타입을 부모 타입으로 선언하면 변수에 다양한 자식 객체가 올 수 있기 때문에 결과가 달라진다.</p>
<p>이를 한 번 코드로 작성해 보았다.</p>
<pre><code class="language-java">// 장난감 클래스
public class Toy {

    Battery leftBattery = new Battery(&quot;왼쪽&quot;, 3);
    Battery rightBattery = new Battery(&quot;오른쪽&quot;, 4);

    int work() {
        System.out.println(&quot;장난감 실행&quot;);
        if (!leftBattery.consumption()) { stop(); return 1;}
        if (!rightBattery.consumption()) { stop(); return 2;}

        return 0;
    }

    void stop() {
        System.out.println(&quot;장난감 작동 중지&quot;);
    }
}

// 배터리 클래스
public class Battery {

    String location;
    int maxLife;
    int currentLife;


    public Battery(String location, int maxLife) {
        this.location = location;
        this.maxLife = maxLife;
    }


    public boolean consumption() {
        ++currentLife;

        if (maxLife &gt; currentLife) {
            System.out.println(location + &quot;남은 배터리 수명 : &quot; + (maxLife - currentLife));
            return true;
        } else {
            System.out.println(location + &quot;배터리 수명이 다됨&quot;);
            return false;
        }
    }
}

// 배터리 자식 클래스1
public class DuraCellBattery extends Battery {

    public DuraCellBattery(String location, int maxLife) {
        super(location, maxLife);
    }

    @Override
    public boolean consumption() {
        ++currentLife;

        if (maxLife &gt; currentLife) {
            System.out.println(location + &quot;듀라셀 배터리 남은 수명 : &quot; + (maxLife - currentLife));
            return true;
        } else {
            System.out.println(location + &quot;듀라셀 배터리 수명이 다됨&quot;);
            return false;
        }

    }
}

// 배터리 자식 클래스2
public class BexelBattery extends Battery {

    public BexelBattery(String location, int maxLife) {
        super(location, maxLife);
    }


    @Override
    public boolean consumption() {
        ++currentLife;

        if (maxLife &gt; currentLife) {
            System.out.println(location + &quot;벡셀 배터리 남은 수명 : &quot; + (maxLife - currentLife));
            return true;
        } else {
            System.out.println(location + &quot;벡셀 배터리 수명이 다됨&quot;);
            return false;
        }
    }
}

// 실행 클래스
public class Example {
    public static void main(String[] args) {
        Toy toy = new Toy();

        for (int i = 1; i &lt; 6; i++) {
            int problemLocation = toy.work();

            switch (problemLocation) {
                case 1:
                    System.out.println(&quot;왼쪽 배터리 듀라셀 배터리로 교체&quot;);
                    toy.leftBattery = new DuraCellBattery(&quot;왼쪽&quot;, 6);
                    break;

                case 2:
                    System.out.println(&quot;오른쪽 배터리 벡셀 배터리로 교체&quot;);
                    toy.rightBattery = new BexelBattery(&quot;오른쪽&quot;, 5);
                    break;
            }

            System.out.println(&quot;------------&quot;);
        }
    }
}</code></pre>
<pre><code>// 출력 결과

장난감 실행
왼쪽남은 배터리 수명 : 2
오른쪽남은 배터리 수명 : 3
------------
장난감 실행
왼쪽남은 배터리 수명 : 1
오른쪽남은 배터리 수명 : 2
------------
장난감 실행
왼쪽배터리 수명이 다됨
장난감 작동 중지
왼쪽 배터리 듀라셀 배터리로 교체
------------
장난감 실행
왼쪽듀라셀 배터리 남은 수명 : 5
오른쪽남은 배터리 수명 : 1
------------
장난감 실행
왼쪽듀라셀 배터리 남은 수명 : 4
오른쪽배터리 수명이 다됨
장난감 작동 중지
오른쪽 배터리 벡셀 배터리로 교체
------------
</code></pre><p>이 코드에서는 다형성을 표현하기 위해 <code>Toy</code> 클래스 내의 왼쪽 배터리와 오른쪽 배터리를 <code>Battery</code> 타입으로 선언했다. 그리고 <code>Toy</code> 객체가 생성될 때는 각각 기본 <code>Battery</code> 객체를 할당했다.</p>
<p>이후 실행 코드에서는 <code>Toy</code> 객체의 <code>work()</code> 메서드를 반복적으로 호출함으로써 배터리 수명이 소모되도록 하였고 수명이 다한 경우에는 각각 <code>DuraCellBattery</code>, <code>BexelBattery</code> 객체로 교체해 주었다. 그 결과, 실행 초반에는 기본 <code>Battery</code> 객체가 사용되다가 중간부터는 자식 클래스인 <code>DuraCellBattery</code>와 <code>BexelBattery</code>의 오버라이딩된 <code>consumption()</code> 메서드가 실행되는 것을 확인할 수 있다.</p>
<p>이처럼 부모 타입의 필드에 자식 객체를 할당할 수 있는 것은 <strong>자동 타입 변환(업캐스팅)</strong> 덕분이며 그로 인해 런타임 시점에 실제 객체 타입에 따라 다른 동작이 발생하는 다형성이 구현된다.</p>
<p>이것이 바로 <strong>필드의 다형성</strong>이다.</p>
<br />
<br />

<h2 id="🎯-매개변수의-다형성">🎯 매개변수의 다형성</h2>
<p>자동 타입 변환은 필드의 다형성뿐만 아니라 메소드의 <strong>매개변수의 다형성</strong>에도 활용이 된다.</p>
<p>아래의 예제를 통해 <strong>매개변수의 다형성</strong>을 살펴보자.</p>
<pre><code class="language-java">
// 군인 클래스
public class Soldier {

    void fight(Gun gun) {
        gun.shoot();
    }
}

// Gun 클래스
public class Gun {
    String name;

    public Gun(String name) {
        this.name = name;
    }

    void shoot() {
        System.out.println(&quot;총을 쐈습니다!&quot;);
    }

}

// AK47 클래스(자식 클래스)
public class AK47 extends Gun {

    public AK47() {
        super(&quot;AK47&quot;);
    }

    @Override
    void shoot() {
        System.out.println(&quot;AK47로 쐈습니다!&quot;);
    }
}

// M16 클래스(자식 클래스)
public class M16 extends Gun {

    public M16() {
        super(&quot;M16&quot;);
    }

    @Override
    void shoot() {
        System.out.println(&quot;M16으로 쐈습니다!&quot;);
    }
}

// 실행 코드
public class Example {
    public static void main(String[] args) {
        Soldier soldier = new Soldier();

        AK47 ak47 = new AK47();
        M16 m16 = new M16();

        soldier.fight(ak47);
        soldier.fight(m16);
    }
}</code></pre>
<pre><code>// 실행 결과
AK47로 쐈습니다!
M16으로 쐈습니다!</code></pre><p>코드를 보면 <code>Soldier</code> 클래스의 fight() 메서드의 매개변수를 <code>Gun</code> 타입으로 선언했다. 이는 <code>Gun</code> 클래스의 자식 클래스인 <code>AK47</code>과 <code>M16</code> 객체도 전달할 수 있다는 의미이다. 이처럼 부모 타입으로 매개변수를 정의하면 다양한 자식 객체를 전달할 수 있어 동일한 메서드 호출이지만 실행 결과는 객체의 실제 타입에 따라 다르게 동작하게 된다.</p>
<p>이것이 바로 <strong>매개변수의 다형성</strong>이다.</p>
<br />
<br />
<br />
<br />


<p>이때까지 다형성에 대해서 알아보았다. 하지만 다형성과 관련해서는 주의해야 할 점도 있다. 바로 <code>강제 타입 변환</code>이다.</p>
<h2 id="🛠️-강제-타입-변환">🛠️ 강제 타입 변환</h2>
<p><strong>강제 타입 변환</strong>은 부모 타입을 자식 타입으로 변환하는 것을 말한다. 즉, 다운 캐스팅이라고 보면 된다.</p>
<p>하지만 모든 부모 타입이 자식 타입으로 강제 타입 변환을 할 수는 없으며 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 자식 타입으로 강제 타입 변환할 때 가능하다.</p>
<p>자식 타입으로 강제 타입 변환하는 방법은 아래와 같다.</p>
<pre><code>자식타입 변수 = (자식타입) 부모타입;
</code></pre><p>이 문법은 기본 타입에서 큰 범위에서 작은 범위로 강제 변환할 때 사용했던 방식과 유사하다. 하지만 객체 다형성에서는 오히려 <strong>작은 범위(부모 타입)에서 큰 범위(자식 타입)</strong>로 변환된다는 점에서 차이가 있다. 하지만 다형성에서의 강제 타입 변환은 단순한 타입 크기의 개념보다는 <strong>객체의 실제 타입에 맞게 명시적으로 변환한다는 점</strong>이 더 중요하다.</p>
<p>다음으로 강제 타입 변환이 어떻게 쓰이는지 코드로 한 번 살펴보자.</p>
<pre><code class="language-java">public class Parent {

    String name;

    public Parent(String name) {
        this.name = name;
    }

    public void method1() {
        System.out.println(&quot;Parent-method1&quot;);
    }
}

public class Child extends Parent {

    String name;

    public Child(String parentName, String childName) {
        super(parentName);
        this.name = childName;
    }

    @Override
    public void method1() {
        System.out.println(&quot;Child-method1&quot;);
    }

    public void method2() {
        System.out.println(&quot;Child-method2&quot;);
    }
}

public class Example {
    public static void main(String[] args) {
        Parent parent = new Child(&quot;부모&quot;, &quot;자식&quot;);
        System.out.println(parent.name); // 부모
        parent.method1(); // Child-method1;
        // parent.method2(); 접근 불가

        // 강제 타입 변환
        Parent parent2 = new Child(&quot;부모2&quot;, &quot;자식2&quot;);
        Child child2 = (Child) parent2;
        child2.method1(); // Child-method1
        child2.method2(); // Child-method2 - 접근 가능

        // 처음부터 자식 타입에 부모를 다운캐스팅하여 할당하려고 하면 ClassCastException이 발생.
        // Child child = (Child) new Parent(&quot;마더&quot;);
        // System.out.println(child.name);
        // child.method1();
        // child.method2();
    }
}</code></pre>
<p>코드를 보면 부모 타입에 자식 객체를 할당하면 부모 타입에 있는 필드와 메소드에만 접근이 가능하다. 그렇기 때문에 parent.method2();로 자식 메소드에 접근할 수 없다. 하지만 다시 다운 캐스팅으로 강제 타입 변환을 한다면 자식 메소드에 접근 가능하다.</p>
<p>반면, 처음부터 자식 타입에 부모를 다운캐스팅하여 할당한 후 자식 클래스의 필드와 메소드에 접근하려고 하면 <code>ClassCastException</code>이 발생하게 된다. 왜냐하면 <code>new Parent</code>로 생성된 객체는 자식 객체가 아닌 부모 객체이다. 그러므로 자식 객체는 존재하지 않기 때문에 예외가 발생하는 것이다.</p>
<p>이러한 점을 주의해서 다형성을 구현할 때 강제 타입 변환을 잘 사용해야 한다.</p>
<br />
<br />

<h3 id="🔍-객체-타입-확인">🔍 객체 타입 확인</h3>
<p>앞서 강제 타입 변환은 객체가 실제로 자식 타입인 경우에만 안전하게 사용할 수 있다는 점을 배웠다. 그렇다면 부모 타입 변수가 참조하는 객체가 실제로 자식 객체인지 확인하는 방법은 없을까? 당연히 java와 같은 객체지향 언어에서는 이러한 기능을 가지고 있다. 그것은 <strong>instanceof 연산자</strong>를 사용하는 것이다.</p>
<p><strong>instanceof 연산자</strong>를 사용하는 방법은 다음과 같다. </p>
<pre><code class="language-java">boolean result = 좌항(객체) instanceof 우항(타입)</code></pre>
<p>이렇게 좌항의 객체가 우항의 인스턴스이면, 즉 우항의 타입으로 객체가 생성되었다면 true를 리턴하고 그렇지 않으면 false를 리턴하는 방식으로 사용한다.</p>
<p><strong>instanceof 연산자</strong>는 주로 매개값의 타입을 조사할 때 사용된다. 메소드 내에서 강제 타입 변환이 필요할 경우 반드시 매개 값이 어떤 객체인지 <strong>instanceof 연산자</strong>로 확인하고 안전하게 강제 타입 변환을 할 수 있다.</p>
<p>다음 예제를 통해 <code>instanceof</code> 연산자가 어떻게 강제 타입 변환 전에 안전성을 확보해주는지 확인해 보자.</p>
<pre><code class="language-java">// 부모 클래스
public class Parent {

    String name;

    public Parent(String name) {
        this.name = name;
    }

    public void method1() {
        System.out.println(&quot;Parent-method1&quot;);
    }
}

// 자식 클래스
public class Child extends Parent {

    String name;

    public Child(String parentName, String childName) {
        super(parentName);
        this.name = childName;
    }

    @Override
    public void method1() {
        System.out.println(&quot;Child-method1&quot;);
    }

    public void method2() {
        System.out.println(&quot;Child-method2&quot;);
    }
}

// 실행 클래스
public class Example {
    // instanceof 연산자 메소드
    public static void method1(Parent parent) {
        if (parent instanceof Child) {
            Child child = (Child) parent;
            System.out.println(child.name);
            System.out.println(&quot;변환 성공&quot;);
        } else {
            System.out.println(&quot;변환 실패&quot;);
        }
    }

    public static void main(String[] args) {
        Parent parent = new Child(&quot;부모&quot;, &quot;자식&quot;);
        method1(parent); // 변환 성공
    }
}</code></pre>
<p>이처럼 <code>instanceof</code> 연산자는 객체의 실제 타입을 확인할 수 있게 해주며 <strong>강제 타입 변환이 필요한 상황에서 안전성을 확보하는 중요한 도구</strong>이다.</p>
<p>특히, 다형성이 적용된 코드에서는 부모 타입의 매개변수나 변수에 자식 객체가 들어올 수 있기 때문에 <strong>잘못된 다운 캐스팅으로 인한 <code>ClassCastException</code>을 방지하기 위해 반드시 사용해야 할 연산자</strong>이다.</p>
<p>즉, <code>instanceof</code>를 통해 타입을 확인하고 나서 강제 타입 변환을 수행하는 것이 객체지향 프로그래밍에서 <strong>안정적이고 견고한 코드</strong>를 작성하는 방법이다.</p>
<br />
<br />



<blockquote>
<p>참조
<a href="https://kephilab.tistory.com/59">https://kephilab.tistory.com/59</a>
혼자 공부하는 자바 - 신용권 (한빛미디어)</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[상속 (Inheritance)]]></title>
            <link>https://velog.io/@hk_98/%EC%83%81%EC%86%8D</link>
            <guid>https://velog.io/@hk_98/%EC%83%81%EC%86%8D</guid>
            <pubDate>Thu, 12 Jun 2025 12:23:51 GMT</pubDate>
            <description><![CDATA[<br />

<h2 id="🧬-상속이란">🧬 상속이란?</h2>
<p>보통 현실에서 상속이라고 하면 부모가 자식에게 무언가를 물려주는 것으로 알고 있을 것이다. 이는 Java와 같은 언어에서도 상속은 부모가 자식에게 물려주는 것과 유사하다고 보면 된다. 하지만 현실에서는 부모가 자식을 선택해서 상속을 해주는 거라면 프로그래밍에서는 자식이 부모를 선택해서 상속을 받는 것이다. </p>
<p>정확하게 말하자면, 부모 클래스(상위 클래스)와 자식 클래스(하위 클래스)가 있을 때, 자식 클래스는 부모 클래스를 선택해서 그 부모의 멤버를 상속받아 그대로 쓸 수 있게 되는 것이다.</p>
<br />

<h3 id="상속의-특징">상속의 특징</h3>
<p><strong>첫째</strong>, 부모 클래스의 <code>private</code> 접근 제한을 갖는 필드 및 메소드는 자식이 물려받을 수 없으며 부모와 자식 클래스가 서로 다른 패키지에 있다면, 부모의 <code>default</code> 접근 제한을 갖는 필드 및 메소드도 자식이 물려받을 수 없다.</p>
<p><strong>둘째</strong>, 다른 상속을 지원하는 언어와 달리 Java는 다중 상속을 지원하지 않는다. 즉, 자식이 여러 부모를 가질 수 없다는 것이다. 이는 다중 상속으로 인해 발생할 수 있는 모호성을 방지하기 위함이다.</p>
<br />
<br />

<h2 id="🔗-상속-받는-법">🔗 상속 받는 법</h2>
<p>상속을 받는 방법은 자식 클래스에 <code>extends</code>를 붙여 상속을 받을 수 있다.</p>
<pre><code class="language-java">public class Animal {
    String species;

    public void eat() {
        System.out.println(&quot;먹이를 먹다&quot;);
    }

    public void sleep() {
        System.out.println(&quot;잠을 잔다&quot;);
    }
}


public class Bird extends Animal {
    String name;
    int age;
    int height;
    int weight;

    public Bird(String species, String name, int age, int height, int weight) {
        this.species = species;
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }


    public void chirp() {
        System.out.println(&quot;새가 운다&quot;);
    }

}


public class ExampleMain {
    public static void main(String[] args) {
        // 자식 객체 생성
        Bird bird = new Bird(&quot;chicken&quot;, &quot;삐약이&quot;, 2, 70, 5);

        // 부모로부터 상속 받은 필드
        System.out.println(bird.species); // chicken

        // 자식 클래스의 필드
        System.out.println(bird.name); // 삐약이
        System.out.println(bird.age); // 2
        System.out.println(bird.height); // 70
        System.out.println(bird.weight); // 5

        // 부모로부터 상속받은 메소드 호출
        bird.eat(); // 먹이를 먹다
        bird.sleep(); // 잠을 잔다


        // 자식 클래스의 메소드 호출
        bird.chirp(); // 새가 운다

    }
}</code></pre>
<p>위의 코드를 보면, extends 키워드를 통해 상속을 받은 자식 클래스는 자신의 필드 및 메서드뿐만 아니라, 부모 클래스의 필드와 메서드에도 직접 접근할 수 있음을 알 수 있다.</p>
<br />
<br />

<h2 id="🏗️-부모-생성자-호출">🏗️ 부모 생성자 호출</h2>
<p>그렇다면 방금의 코드에서는 자식의 생성자만 호출한 다음 부모의 필드와 메소드에 접근을 할 수 있었는데 부모에 접근을 하려면 부모도 생성이 되어야 하지 않나라고 의문을 품을 수 있다. 결론부터 말하면 부모도 생성이 되어있는 상태이다.</p>
<p>그러면 부모가 어떻게 생성이 되는지 궁금할 것이다. 그것은 자식 생성자의 맨 첫 줄에 <code>super()</code>라는 것이 생략되어 있으며 이로 인해 부모 클래스의 기본 생성자가 자동으로 호출되기 때문이다.</p>
<pre><code class="language-java">public class Bird extends Animal {
    String name;
    int age;
    int height;
    int weight;

    public Bird(String species, String name, int age, int height, int weight) {
        super(species); // 이것이 생략 되어있음.
        this.species = species;
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }


    public void chirp() {
        System.out.println(&quot;새가 운다&quot;);
    }

}</code></pre>
<p>위의 코드처럼 <code>super(species);</code>가 생략되어 있는 것이다.</p>
<br />

<h3 id="super">super</h3>
<p><code>super</code>란, 부모 클래스로부터 상속받은 필드나 메소드를 자식 클래스에서 참조하는 데 사용하는 참조 변수를 말한다. 주로 부모 클래스의 생성자와 메소드를 호출하거나 필드에 접근할 때 사용된다. this의 경우 자기 자신을 가리킨다고 하면 <code>super</code>의 경우 부모를 가리킨다고 보면 된다.</p>
<p>생성자의 경우는 위의 코드처럼 호출하는 것이다.</p>
<p>메소드와 필드의 경우는 아래의 코드로 보자.</p>
<pre><code class="language-java">public class Animal {
    String species;

    // 이것이 없으면 컴파일 오류가 발생된다.
    public Animal(String species) { 
        this.species = species;
    }

    public void eat() {
        System.out.println(&quot;먹이를 먹다&quot;);
    }

    public void sleep() {
        System.out.println(&quot;잠을 잔다&quot;);
    }
}

public class Bird extends Animal {
    String name;
    int age;
    int height;
    int weight;

    public Bird(String species, String name, int age, int height, int weight) {
        super(species); // 이것이 생략 되어있음.
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }


    public void chirp() {
        System.out.println(&quot;새가 운다&quot;);
    }

    public void oneDayLife() {
        super.eat();
        this.chirp();
        super.sleep();
    }

    public void printSpecies() {
        System.out.println(super.species);
    }
}

public class ExampleMain {
    public static void main(String[] args) {
        // 자식 객체 생성
        Bird bird = new Bird(&quot;chicken&quot;, &quot;삐약이&quot;, 2, 70, 5);

        // 메소드 접근 결과
        bird.oneDayLife(); // 먹이를 먹다 - 새가 운다 - 잠을 잔다

        // 필드 접근 결과
        bird.printSpecies(); // chicken
    }
}</code></pre>
<p>여기서 주의할 점으로는 만약 부모 클래스에 매개변수를 받는 생성자만 정의되어 있을 경우, 자식 클래스에서는 반드시 <code>super(매개변수)</code>를 통해 부모 생성자를 명시적으로 호출해야 한다. 그렇지 않으면 컴파일 오류가 발생하기에 이 점에 유의해야 한다.</p>
<br />
<br />

<h2 id="✏️-메소드-재정의override">✏️ 메소드 재정의(Override)</h2>
<p>또한 상속에서는 자식에서 부모로부터 받은 메소드를 재정의 할 수 있는데 이것을 메소드 오버라이딩이라고도 한다. 주로 부모로 받은 메소드가 적절하지 않을 때 자식 클래스의 현 상황에 맞게 재정의하여 사용하는 기능이다.</p>
<p>코드로 알아보자.</p>
<pre><code class="language-java">public class Animal {
    String species;

    public void eat() {
        System.out.println(&quot;먹이를 먹다&quot;);
    }

    public void sleep() {
        System.out.println(&quot;잠을 잔다&quot;);
    }
}


public class Bird extends Animal {
    String name;
    int age;
    int height;
    int weight;

    public Bird(String species, String name, int age, int height, int weight) {
        super(species); // 이것이 생략 되어있음.
        this.species = species;
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }


    public void chirp() {
        System.out.println(&quot;새가 운다&quot;);
    }

    @Override
    public void eat() {
        System.out.println(&quot;새가 먹이를 먹다&quot;);
    }
}


public class ExampleMain {
    public static void main(String[] args) {
        // 자식 객체 생성
        Bird bird = new Bird(&quot;chicken&quot;, &quot;삐약이&quot;, 2, 70, 5);

        bird.eat(); // 새가 먹이를 먹다.
    }
}</code></pre>
<p>위의 코드에서처럼 보통은 어노테이션을 활용하여 @Override를 사용해서 메소드 재정의를 한다.
물론 어노테이션을 사용하지 않아도 되지만 @Override를 사용하게 되면 메소드가 정확히 재정의 된 것인지 컴파일러가 확인해 주기 때문에 실수를 줄여준다.</p>
<br />

<p>하지만 이렇게 메소드를 재정의할 때 주의해야 할 점이 있다.</p>
<p><strong>첫째</strong>, 부모의 메소드와 동일한 리턴 타입, 메소드 명, 매개 변수 목록을 가져야 한다.
<strong>둘째</strong>, 접근 제한을 더 강하게 정의할 수 없다. 반대로 약하게는 가능하다.
<strong>셋째</strong>, 새로운 예외를 throw 할 수 없다.</p>
<p>이 세가지에 잘 유의해서 메소드 재정의를 해야한다.</p>
<br />
<br />

<blockquote>
<p>참조
<a href="https://chanhuiseok.github.io/posts/java-1/">https://chanhuiseok.github.io/posts/java-1/</a>
<a href="https://wikidocs.net/280">https://wikidocs.net/280</a>
<a href="https://erinh.tistory.com/entry/Java-%EC%9E%90%EB%B0%94-7-%EC%83%81%EC%86%8D-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%81%EC%86%8D-super-%EB%A9%94%EC%86%8C%EB%93%9C-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9">https://erinh.tistory.com/entry/Java-%EC%9E%90%EB%B0%94-7-%EC%83%81%EC%86%8D-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%81%EC%86%8D-super-%EB%A9%94%EC%86%8C%EB%93%9C-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9</a>
혼자 공부하는 자바(한빛 미디어) - 신용권</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 15683 감시]]></title>
            <link>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-15683-%EA%B0%90%EC%8B%9C</link>
            <guid>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-15683-%EA%B0%90%EC%8B%9C</guid>
            <pubDate>Mon, 09 Jun 2025 13:04:17 GMT</pubDate>
            <description><![CDATA[<p>오늘은 감시라는 문제를 풀어보았다. 백트래킹 문제이며 현재 기준으로 골드 3 문제이다. 골드 4에서 골드 3으로 오자마자 구현에서 많은 것을 요구하여 힘들었던 문제였다.</p>
<br />
<br />

<h2 id="🔥-문제">🔥 문제</h2>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/15683">https://www.acmicpc.net/problem/15683</a></p>
</blockquote>
<p>스타트링크의 사무실은 1×1크기의 정사각형으로 나누어져 있는 N×M 크기의 직사각형으로 나타낼 수 있다. 사무실에는 총 K개의 CCTV가 설치되어져 있는데, CCTV는 5가지 종류가 있다. 각 CCTV가 감시할 수 있는 방법은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/079efa6f-30e0-403b-8505-076849e6f76d/image.png" alt=""></p>
<p>1번 CCTV는 한 쪽 방향만 감시할 수 있다. 2번과 3번은 두 방향을 감시할 수 있는데, 2번은 감시하는 방향이 서로 반대방향이어야 하고, 3번은 직각 방향이어야 한다. 4번은 세 방향, 5번은 네 방향을 감시할 수 있다.</p>
<p>CCTV는 감시할 수 있는 방향에 있는 칸 전체를 감시할 수 있다. 사무실에는 벽이 있는데, CCTV는 벽을 통과할 수 없다. CCTV가 감시할 수 없는 영역은 사각지대라고 한다.</p>
<p>CCTV는 회전시킬 수 있는데, 회전은 항상 90도 방향으로 해야 하며, 감시하려고 하는 방향이 가로 또는 세로 방향이어야 한다.</p>
<p>
    <img src="https://velog.velcdn.com/images/hk_98/post/e9f283a6-fe36-4a3c-965e-b75a9109a9bd/image.png" width="200" />
</p>

<p>지도에서 0은 빈 칸, 6은 벽, 1~5는 CCTV의 번호이다. 위의 예시에서 1번의 방향에 따라 감시할 수 있는 영역을 &#39;#&#39;로 나타내면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/dd9a92f0-3cb9-4071-86d9-81e91cb5b622/image.png" alt=""></p>
<p>CCTV는 벽을 통과할 수 없기 때문에, 1번이 → 방향을 감시하고 있을 때는 6의 오른쪽에 있는 칸을 감시할 수 없다.</p>
<p>
    <img src="https://velog.velcdn.com/images/hk_98/post/8f7d9c1d-ba1b-4f46-8b9b-3061b5a87780/image.png" width="200" />
</p>

<p>위의 예시에서 감시할 수 있는 방향을 알아보면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/febbd049-9e93-426f-9918-bd1287aaac04/image.png" alt=""></p>
<p>CCTV는 CCTV를 통과할 수 있다. 아래 예시를 보자.</p>
<p>
    <img src="https://velog.velcdn.com/images/hk_98/post/bc43302c-6bf6-424e-bf7b-9d772e4271c2/image.png" width="200" />
</p>

<p>위와 같은 경우에 2의 방향이 ↕ 3의 방향이 ←와 ↓인 경우 감시받는 영역은 다음과 같다.</p>
<p>
    <img src="https://velog.velcdn.com/images/hk_98/post/3e062fb6-f1fe-4575-92ba-467eb728d911/image.png" width="200" />
</p>


<p>사무실의 크기와 상태, 그리고 CCTV의 정보가 주어졌을 때, CCTV의 방향을 적절히 정해서, 사각 지대의 최소 크기를 구하는 프로그램을 작성하시오.</p>
<br />

<h2 id="입력">입력</h2>
<p>첫째 줄에 사무실의 세로 크기 N과 가로 크기 M이 주어진다. (1 ≤ N, M ≤ 8)</p>
<p>둘째 줄부터 N개의 줄에는 사무실 각 칸의 정보가 주어진다. 0은 빈 칸, 6은 벽, 1~5는 CCTV를 나타내고, 문제에서 설명한 CCTV의 종류이다. </p>
<p>CCTV의 최대 개수는 8개를 넘지 않는다.</p>
<br />

<h2 id="출력">출력</h2>
<p>첫째 줄에 사각 지대의 최소 크기를 출력한다.</p>
<br />
<br />
<br />

<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>처음에 cctv가 방향별로 감시했는 부분을 표시한 후 다음 조합을 구할 때 기존의 값을 지우는 방법으로 구현하려고 했으나 그렇게 하면 너무 복잡해져서 다른 방법을 고민했지만 도저히 생각이 안 나서 다른 분의 코드를 참조하였다.</p>
<br />

<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;


public class Main {
    public static class Cctv {
        int x;
        int y;
        int type;

        public Cctv(int x, int y, int type) {
            this.x = x;
            this.y = y;
            this.type = type;
        }
    }

    static int N;
    static int M;

    static ArrayList&lt;Cctv&gt; cctvs = new ArrayList&lt;&gt;();

    // 상하좌우
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1};

    static int[][][] direction = {{{0}}, {{0}, {1}, {2}, {3}}, {{0, 1}, {2, 3}}, {{0, 3}, {1, 3}, {1, 2}, {0, 2}}, {{0, 2, 3}, {0, 1, 3}, {1, 2, 3}, {0, 1, 2}}, {{0, 1, 2, 3}}};

    static int result = Integer.MAX_VALUE;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        StringTokenizer info = new StringTokenizer(br.readLine());

        N = Integer.parseInt(info.nextToken());
        M = Integer.parseInt(info.nextToken());

        int[][] arr = new int[N][M];

        for (int i = 0; i &lt; N; i++) {
            StringTokenizer nums = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; M; j++) {
                int num = Integer.parseInt(nums.nextToken());
                arr[i][j] = num;
                // cctv 정보 저장
                if (num != 0 &amp;&amp; num != 6) {
                    cctvs.add(new Cctv(i, j, num));
                }
            }
        }

        dfs(0, arr);

        System.out.println(result);

    }

    // 조합
    private static void dfs(int depth, int[][] map) {
        // 종료조건
        if (depth == cctvs.size()) {
            int count = countZero(map);
            result = Math.min(count, result);
            return;
        }

        int type = cctvs.get(depth).type;
        int x = cctvs.get(depth).x;
        int y = cctvs.get(depth).y;

        for (int i = 0; i &lt; direction[type].length; i++) {

            int[][] newMap = copyArr(map);

            for (int j = 0; j &lt; direction[type][i].length; j++) {

                int dir = direction[type][i][j];
                int nx = x + dx[dir];
                int ny = y + dy[dir];

                while (true) {

                    if (nx &lt; 0 || nx &gt;= N || ny &lt; 0 || ny &gt;= M) {
                        break;
                    }

                    if (map[nx][ny] == 6) {
                        break;
                    }

                    newMap[nx][ny] = -1;
                    nx += dx[dir];
                    ny += dy[dir];
                }
            }

            dfs(depth + 1, newMap);
        }
    }

    private static int[][] copyArr(int[][] map) {
        int[][] newMap = new int[N][M];

        for (int i = 0; i &lt; N; i++) {
            for (int j = 0; j &lt; M; j++) {
                newMap[i][j] = map[i][j];
            }
        }

        return newMap;
    }

    private static int countZero(int[][] map) {
        int count = 0;
        for (int i = 0; i &lt; N; i++) {
            for (int j = 0; j &lt; M; j++) {
                if (map[i][j] == 0) {
                    count++;
                }
            }
        }

        return count;
    }
}
</code></pre>
<p>위의 코드를 보자면 cctv의 타입별로 방향을 3차원 배열로 저장한 다음 dfs로 조합을 하면서 구현하였다. 표시할 배열은 다음 cctv의 감시 범위를 표시하기 전에 깊은 복사로 배열을 하나 더 만들서 표시하였다. 다음으로 depth가 cctv의 갯수와 같다면 결과를 구하고 재귀를 종료하는 방식이다.</p>
<p>이 문제를 풀면서 구현력을 더 발전시켜야 할 것 같다....</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[final과 상수]]></title>
            <link>https://velog.io/@hk_98/final%EA%B3%BC-%EC%83%81%EC%88%98</link>
            <guid>https://velog.io/@hk_98/final%EA%B3%BC-%EC%83%81%EC%88%98</guid>
            <pubDate>Mon, 02 Jun 2025 10:09:29 GMT</pubDate>
            <description><![CDATA[<h1 id="🔒-final">🔒 final</h1>
<p>java에서 final 이란? 단어 의미 그대로 최종적이라는 의미로 한 번 할당되면 변경할 수 없도록 하는 키워드이다. final 키워드는 변수, 메소드, 클래스에 사용될 수 있는 특징을 가진다.</p>
<br />

<p>이렇게만 설명하면 이해가 잘 안될 수도 있으니 변수, 메소드, 클래스에 사용되는 예시로 바로 알아보자.</p>
<h2 id="변수">변수</h2>
<pre><code class="language-java">public class Person {
    private String name;
    private int age;

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

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


public class Example {

    public static void main(String[] args) {
        // 초기화
        final int num = 1; // 필드 선언 시 값을 주는 방법으로 초기화
        final Person person = new Person(&quot;민수&quot;, 23); // 생성자로 초기화

        System.out.println(num); // 1
        System.out.println(person.getName()); // &quot;민수&quot;
        System.out.println(person.getAge()); // 23

        num = 2; // 컴파일 에러
        person.setName(&quot;철수&quot;); // 이렇게 setter를 통해 객체 내부의 값은 변경 가능!
        person.setAge(25);

        System.out.println(person.getName());
        System.out.println(person.getAge());

        // 하지만 참조값(주소값)을 바꾸는 것은 불가능!
        person = new Person(&quot;영수&quot;, 29); // 컴파일 에러
    }

}
</code></pre>
<p>먼저 변수의 경우 변수에 final을 붙이면 이 변수(필드)는 수정할 수 없다는 의미이다. 먼저 final이 붙은 변수에 초기화를 방법은 값을 할당하거나 생성자를 통해서 값을 주는 방법이 있다.</p>
<p>위의 코드에서 볼 수 있듯이 일반적인 변수의 경우 final이 붙은 변수에 최초로 값을 초기화하고 나서 그 뒤에 값을 변경하면 컴파일 에러가 발생한다. </p>
<p>하지만 생성자로 초기화 한 변수의 경우는 최초로 생성자를 이용하여 객체를 생성한 후 참조 값을 할당한 다음 setter를 통해 인스턴스 변수의 값을 바꿔도 컴파일 에러가 발생하지 않고 값이 변경이 된다. 하지만 다른 객체를 생성하여 변수에 할당되어 있는 참조 값을 변경하게 되면 컴파일 에러가 발생하게 된다.</p>
<br />
<br />


<h2 id="메소드">메소드</h2>
<pre><code class="language-java">public class Parent {
    public final void say() {
        System.out.println(&quot;안녕하세요!&quot;);
    }
}

public class Child extends Parent {

    @Override // final 메서드는 오버라이드 불가
    public void say() {
        System.out.println(&quot;반갑습니다!&quot;);
    }

    public void greet() {
        say(); // 부모의 say()를 그대로 사용
        System.out.println(&quot;자식 클래스에서 추가 동작&quot;);
    }
}</code></pre>
<p>메소드의 경우는 final이 붙은 메소드면 오버라이드가 불가능해진다. 하지만 상속은 가능하다. 그래서 위의 코드처럼 Child의 코드에서 greet 메소드는 say()를 상속받아서 메소드를 실행할 수 있지만 say()를 오버라이드 해서 사용하려고 하면 컴파일 에러가 발생하게 된다.</p>
<br />
<br />



<h2 id="클래스">클래스</h2>
<pre><code class="language-java">
public final class Parent {
    ...
}


public class Child extends Parent { // 컴파일 에러
    ...
}
</code></pre>
<p>클래스의 경우 final을 사용하게 된다면 상속받을 수 없게 된다. 위의 코드처럼 부모 클래스에 final 선언이 되어 있으면 자식이 상속받지 못한다는 의미이다.</p>
<br />
<br />
<br />

<h1 id="📌-상수">📌 상수</h1>
<p>방금까지 java에서의 final에 대해서 알아보았는데 그러면 상수는 무엇인가? java 사용하면서 final과 같이 상수라는 것을 몇 번 들어봤었는데 이 또한 final처럼 이해하고 있지 않아서 한 번 공부해 보았다. </p>
<p>알아보니 수학에서도 자주 나오던 개념이었다. 수학에서는 수식에서 변하지 않는 값을 일컫는데 java에서 사용하는 상수도 이와 거의 같은 의미이다. java에서의 상수는 프로그램 실행 도중 값이 절대 바뀌지 않는 변수라는 의미이다. 상수는 코드의 가독성을 높이고, 변경되지 않는 값들을 한 곳에서 관리할 수 있어 유지보수에도 용이하다.</p>
<p>예를 들어, 프로그램에서 설정값과 같이 바꾸지 않을 어떤 기준 값이라든지 원주율과 같이 항상 고정된 값이 필요할 경우 사용한다.</p>
<pre><code class="language-java">
public class Constants {
    public static final double PI = 3.14159;
    public static final int MAX_USER = 100;
}
</code></pre>
<p>위의 코드처럼 상수는 final과 함께 사용되는데 프로그램 어디서든 Constants.PI와 Constants.MAX_USER와 같이 클래스 이름을 접두사로 붙여 접근할 수 있고 해당 값은 절대 변하지 않는다. 또한 한곳에서 사용한다면 유지 보수도 용이하다.</p>
<br />
<br />

<h2 id="상수의-특징">상수의 특징</h2>
<ul>
<li><span style="color:orange"><strong>변경 불가</strong></span> : <code>final</code> 키워드로 선언된 변수는 한 번 값이 설정되면 변경할 수 없다.</li>
<li><span style="color:orange"><strong>읽기 전용</strong></span> : 상수는 초기화된 이후로 읽기 전용이다.</li>
<li><span style="color:orange"><strong>클래스 수준 상수</strong></span> : <code>static</code> 키워드를 사용하여 클래스 수준에서 선언할 수 있다. 이는 상수를 모든 인스턴스에서 공통으로 사용하도록 한다.</li>
<li><span style="color:orange"><strong>불변성</strong></span> : 상수의 불변성은 프로그램의 안정성을 높여준다. 예를 들어, 중요한 설정 값들이 변경되지 않도록 한다.</li>
</ul>
<br />
<br />

<h2 id="변수와-상수의-차이점">변수와 상수의 차이점</h2>
<br />

<h3 id="사용-목적">사용 목적</h3>
<ul>
<li><span style="color:orange"><strong>상수(Constant)</strong></span> : 상수는 변경되지 않는 값을 표현할 때 사용된다. 예를 들어, 물리 상수, 수학 상수, 설정 값 등이 해당된다. 상수를 사용함으로써 코드의 가독성과 안정성이 향상된다.</li>
</ul>
<ul>
<li><span style="color:orange"><strong>변수(Variable)</strong></span> : 변수는 프로그램의 상태나 데이터를 저장하고 변경할 때 사용된다. 예를 들어, 사용자 입력 값, 계산 결과, 임시 데이터 등이 해당된다.</li>
</ul>
<br />

<h3 id="메모리-사용">메모리 사용</h3>
<ul>
<li><p><span style="color:orange"><strong>상수(Constant)</strong></span> : 상수는 프로그램이 시작될 때 메모리에 로드되고, 프로그램이 종료될 때까지 유지된다. 일반적으로 클래스 로드 시 한 번만 초기화된다. <strong>(메소드(클래스) 영역)</strong></p>
</li>
<li><p><span style="color:orange"><strong>변수(Variable)</strong></span> : 변수는 프로그램 실행 중에 필요할 때 메모리에 할당되고, 더 이상 필요하지 않을 때 해제된다. 로컬 변수는 메서드 호출 시 생성되고, 메서드가 종료되면 해제된다. <strong>(스택 영역(로컬 변수), 힙 영역(인스턴스 변수))</strong></p>
</li>
</ul>
<br />
<br />
<br />


<blockquote>
<p><strong>참조</strong>
<a href="https://sabarada.tistory.com/148">https://sabarada.tistory.com/148</a>
<a href="https://tisue.tistory.com/11">https://tisue.tistory.com/11</a>
혼자 공부하는 자바</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[인스턴스 멤버와 정적 멤버]]></title>
            <link>https://velog.io/@hk_98/%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EB%A9%A4%EB%B2%84%EC%99%80-%EC%A0%95%EC%A0%81-%EB%A9%A4%EB%B2%84</link>
            <guid>https://velog.io/@hk_98/%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EB%A9%A4%EB%B2%84%EC%99%80-%EC%A0%95%EC%A0%81-%EB%A9%A4%EB%B2%84</guid>
            <pubDate>Fri, 30 May 2025 10:53:13 GMT</pubDate>
            <description><![CDATA[<h2 id="🧰-인스턴스-멤버">🧰 인스턴스 멤버</h2>
<p>인스턴스 멤버란, 각 객체마다 존재하는 멤버 변수나 메서드이다. 이들을 각각 인스턴스 필드, 인스턴스 메소드라고도 부른다. 이러한 멤버 변수와 메서드는 객체를 생성해야 접근이 가능하다.</p>
<br />

<p>선언 방법으로는 아래와 같다.</p>
<pre><code class="language-java">public class Person {

    String name; // 인스턴스 필드
    int age; // 인스턴스 필드

    Person(String name, int age) { // 생성자
        this.name = name;
        this.age = age;
    }

    void run() { // 인스턴스 메서드
        System.out.println(&quot;뛴다.&quot;);
    }

}


public class PersonExample {
    public static void main(String[] args) {
        Person person = new Person(&quot;민수&quot;, 23);
        System.out.println(person.name); // &quot;민수&quot;
        System.out.println(person.age); // 23
        person.run(); // &quot;뛴다.&quot;


        Person other = new Person(&quot;철수&quot;, 24);
        System.out.println(other.name); // &quot;철수&quot;
        System.out.println(other.age); // 24
        other.run(); // &quot;뛴다.&quot;
    }
}</code></pre>
<p>위와 같이 Person 클래스에서 name, age, run() 등으로 인스턴스 필드와 인스턴스 메소드를 선언할 수 있다.</p>
<p>그리고 PersonExample의 코드처럼 생성자로 객체를 생성한 다음 person과 other 등의 참조 변수로 접근을 하여 사용할 수 있다.</p>
<br />

<p>이와 같이 코드가 실행된다면 메모리에서는 아래의 그림과 같이 객체가 생성되어 있다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/228bade9-fb9d-4ca1-9901-2a735373be44/image.png" width="60%"/></p>

<p>그림에서 볼 수 있듯이 참조 변수 person과 other은 스택 영역에 생성된 객체들은 힙 영역에 객체 안에 있는 메소드는 메소드 영역에 각각 생성된다. 또한 인스턴스 필드는 객체마다 따로 존재하고, 인스턴스 메소드 run()은 메소드 영역에 저장되어 공유된다. </p>
<p>여기서 인스턴스 메소드는 왜 메소드 영역에 저장되는지 궁금할 수 있다. 인스턴스 메소드는 객체에 소속된 멤버인데, 왜 객체 내부에 존재하지 않고 메소드 영역에 저장되고 공유가 될까? 그 이유는 메소드는 코드 블록이므로 객체마다 동일한 코드 블록을 가지고 있을 이유가 없기 때문이다. </p>
<p>그러면 왜 인스턴스 메소드에 인스턴스라는 용어를 붙였을까? 이에 대한 답으로는 메모리 블록 내부에 인스턴스 필드 등이 사용되는 경우가 있기 때문이다. 메소드에 인스턴스 필드가 사용되는 경우 객체가 없으면 실행될 수 없다.</p>
<br />

<h2 id="↩️-this">↩️ this</h2>
<p>방금 코드에서 생성자 내부에서 this를 사용하여 생성자가 실행되는 것을 볼 수 있다. this는 영어 의미 그대로 이것이라는 것을 사용했다고 보면 된다. 즉, this는 객체 자신을 참조한다는 의미이다. 주로 인스턴스 필드와 메소드의 매개 변수의 이름이 동일할 경우, 인스턴스 멤버인 필드임을 명시하고자 할 때 사용된다. 코드에서 보면 person이라는 객체를 생성할 때 생성자에서 this는 person을 가리킨다. 그래서 person 객체에서 this.name이라고 하면 person 객체의 name 인스턴스 필드라는 의미이다.</p>
<br />
<br />
<br />

<h2 id="📌-정적-멤버">📌 정적 멤버</h2>
<p>정적 멤버는 객체가 아닌 클래스에 고정(static)된 멤버이다. 따라서 객체를 생성하지 않고 static 필드나 메소드를 사용할 수 있다. 이들을 각각 정적 필드, 정적 메소드라고 부른다.</p>
<br />

<p>아래는 정적 필드와 정적 메소드를 선언한 코드이다.</p>
<pre><code class="language-java">public class Person {
    static int totalPerson = 0; // 정적 필드

    String name;
    int age;

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

    void run() {
        System.out.println(&quot;뛴다.&quot;);
    }

    static int getTotalPerson() { // 정적 메소드
        return totalPerson;
    }
}

public class PersonExample {
    public static void main(String[] args) {
        Person person = new Person(&quot;민수&quot;, 23);
        Person other = new Person(&quot;철수&quot;, 24);
        Person other2 = new Person(&quot;준석&quot;, 29);

        System.out.println(Person.totalPerson); // 3 출력
        System.out.println(Person.getTotalPerson()); // 3 출력

        System.out.println(other.totalPerson); // 이렇게 인스턴스를 통해서도 접근할 수 있지만
        System.out.println(other.getTotalPerson()); // 클래스 명으로 접근하는 것을 권장.
        System.out.println(other2.totalPerson);
        System.out.println(other2.getTotalPerson());
    }
}</code></pre>
<p>위의 코드에서 볼 수 있듯이 특징으로는 정적 멤버는 클래스에 1개만 존재하며 모든 인스턴스가 공유할 수 있다. </p>
<p>또한 객체 없이 클래스명으로 접근할 수 있으며 인스턴스명으로도 접근이 가능하지만 클래스명으로 접근하는 것이 권장된다. (클래스 전체에 공통된 값이라는 의도가 분명하기 때문) </p>
<p>주로 상수, 카운터, 유틸리티 기능에 사용된다.</p>
<br />
<br />


<p>메모리 상에서는 정적 멤버는 클래스에 고정되어 있으므로 클래스 로더가 클래스(바이트 코드)를 로딩해서 메소드 영역에 적재할 때 클래스 별로 관리된다. 따라서 객체 생성 없이 사용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/622e3480-004f-4531-ab83-8ed60cf825ff/image.png" width="60%" /></p>

<br />

<p>마지막으로 정적 메소드 선언 시 주의할 점이 있다. 객체가 없어도 실행되기 때문에 정적 메소드 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없는 경우가 있다.</p>
<p>코드로 알아보자.</p>
<pre><code class="language-java">public class Person {
    static int totalPerson = 0;

    String name;
    int age;

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

    void run() {
        System.out.println(&quot;뛴다.&quot;);
    }

    static int getTotalPerson() {
        this.name = &quot;민수&quot;; // 컴파일 에러
        this.run(); // 컴파일 에러
        return totalPerson;
    }
}</code></pre>
<p>이와 같이 정적 메소드 내부에서 객체를 생성하지 않고 인스턴스 필드와 인스턴스 메소드를 사용하게 된다면 컴파일 에러가 발생한다. 또한 this도 사용이 불가하다. 만약 정적 메소드에서 인스턴스 멤버를 사용하고 싶다면 객체를 먼저 생성한 다음 참조 변수를 사용한다면 가능하다. </p>
<br />
<br />
<br />


<blockquote>
<p>참조
혼자 공부하는 자바
<a href="https://bambookim.tistory.com/17">https://bambookim.tistory.com/17</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[java 메모리 영역]]></title>
            <link>https://velog.io/@hk_98/java-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%98%81%EC%97%AD</link>
            <guid>https://velog.io/@hk_98/java-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%98%81%EC%97%AD</guid>
            <pubDate>Tue, 20 May 2025 11:23:53 GMT</pubDate>
            <description><![CDATA[<p>오늘은 java의 메모리 영역에 대해서 공부해 보았다. 평소에도 java의 메모리 영역에서 간단하게는 알고 있었는데 좀 더 자세히 공부할 필요가 있다고 생각하여 글을 작성하게 되었다.</p>
<br />

<h2 id="📚-jvm">📚 JVM</h2>
<p>java의 메모리 영역에 대해서 알기 전 JVM이라는 것에 대해서 알 필요가 있다.</p>
<p><strong>JVM(Java Virtual Machine)</strong>이란 자바 가상 머신이며 Java의 바이트 코드를 해석하고 실행하는 역할을 한다. JVM은 OS로부터 메모리를 할당받고, 그 메모리를 용도에 따라서 여러 영역으로 나누어 관리를 한다.</p>
<p>이러한 JVM 덕분에 다양한 운영체제에서 java 파일을 실행할 수 있다. </p>
<br />

<h3 id="jvm-실행-순서">JVM 실행 순서</h3>
<ol>
<li>.java 파일을 컴파일러(Compiler)를 통해 .class 파일로 변환한다.</li>
<li>.class 파일을 JVM의 ClassLoader(클래스로더)에게 보낸다.</li>
<li>ClassLoader에서 JVM 런타임 영역으로 로딩(할당)하여 메모리에 올린다.</li>
</ol>
<br />

<h3 id="jvm의-runtime-data-area런타임-데이터-영역">JVM의 RunTime Data Area(런타임 데이터 영역)</h3>
<ul>
<li>Java 메모리 영역에는 총 5가지 영역이 있다.</li>
</ul>
<ol>
<li>Static Area </li>
<li>Heap Area</li>
<li>Stack Area</li>
<li>PC Register</li>
<li>Native Method Stack</li>
</ol>
<p>이 중에서 일반적인 자바 개발자가 가장 자주 접하게 되는 메모리 영역은 <span style="color:orange"><strong>Static, Heap, Static</strong></span> 영역 등이 있다. 그래서 이 글에서는 3가지 영역에 대해서 작성해볼까 한다.</p>
<br />

<p>
<img src="https://velog.velcdn.com/images/hk_98/post/4d80161d-970b-438e-8f16-5294975b20b9/image.png" width="80%">
</p>

<br />
<br />

<h2 id="🎫-자바-변수의-종류">🎫 자바 변수의 종류</h2>
<p>3가지의 메모리 영역을 소개하기 전에 자바 변수의 종류에 대해서 알고 가는 것이 좋을듯 하여 잠깐 알아보자.</p>
<pre><code class="language-java">public class Person {

    private String name; // 인스턴스 변수
    private static int age = 0; // 정적(클래스) 변수

    public void setName(String name) { // 매개변수
        this.name = name;
    }

    public int getNextYearAge() {
        int next = 0; // 지역변수

        return next + 1;
    }
}</code></pre>
<br />

<h3 id="변수의-종류와-메모리-영역">변수의 종류와 메모리 영역</h3>
<table>
<thead>
<tr>
<th>변수명</th>
<th>선언위치</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>클래스 변수</strong><br>(class variable, static 변수)</td>
<td>메소드 영역</td>
<td>클래스 영역에서 타입 앞에 <code>static</code>이 붙는 변수<br>객체를 공유하는 변수로, 여러 객체에서 공통으로 사용하고 싶을 때 정의</td>
</tr>
<tr>
<td><strong>인스턴스 변수</strong><br>(instance variable)</td>
<td>힙 영역</td>
<td>클래스 영역에서 <code>static</code>이 아닌 변수<br>개별적인 저장 공간으로 객체/인스턴스마다 다른 값 저장 가능<br>※ 객체/인스턴스 생성만 하고 참조 변수가 없는 경우 가비지 컬렉터에 의해 자동 제거됨</td>
</tr>
<tr>
<td><strong>지역 변수</strong><br>(local variable)</td>
<td>스택 영역</td>
<td>메서드 내에서 선언되고 메서드 수행이 끝나면 소멸되는 변수<br>초깃값을 지정한 후 사용할 수 있음</td>
</tr>
<tr>
<td><strong>매개 변수</strong><br>(parameter)</td>
<td>스택 영역</td>
<td>메서드 호출 시 &#39;전달하는 값&#39;을 가지고 있는 인수<br>(지역 변수처럼 선언된 곳부터 수행이 끝날 때까지 유효함)</td>
</tr>
</tbody></table>
<p>각 변수의 생성시기는 아래와 같다.</p>
<ul>
<li>클래스변수 : 클래스가 메모리에 올라갈 때</li>
<li>인스턴스변수 : 인스턴스가 생성되었을 때</li>
<li>지역변수 / 매개변수 : 위치하고 있는 메서드가 수행되었을 때</li>
</ul>
<br />
<br />

<h2 id="🧱-staticmethod-영역">🧱 Static(Method) 영역</h2>
<ul>
<li>JVM이 실행된 후 클래스가 로딩될 때 생성된다.</li>
<li>JVM이 읽어들인 클래스와 인터페이스에 대한 런타임 상수 풀, 클래스 변수(static 변수), 상수(final), 메소드(method) 등을 저장하는 공간.</li>
<li>Static(Method) 영역에 있는 것은 어느 곳에서나 접근 가능</li>
<li>Static(Method) 영역에 있는 데이터들은 프로그램이 종료할 때까지 메모리에 남아있다. 그래서 static 메모리에 있는 데이터들은 프로그래밍 종료될 때까지 어디서든 사용이 가능하다. 그러나 static 데이터를 무분별하게 많이 사용하게 되면 메모리 부족 현상이 발생하게 된다.</li>
</ul>
<br />
<br />


<h2 id="🧺-heap-영역">🧺 Heap 영역</h2>
<ul>
<li>참조형 데이터 객체(타입 -&gt; String, 배열, Enum, Object)의 실제 데이터가 저장되는 공간이다. Stack 영역에서 실제 데이터가 존재하는 Heap 영역의 참조 값을 가지고 있다.</li>
<li>new로 인스턴스를 생성할 때, Heap 영역에는 생성된 객체가 저장되며 Stack 영역에서 생성된 객체에 대한 주소 값(Reference)이 저장된다.</li>
<li>만일 참조하는 변수나 필드가 없다면 의미 없는 개체가 되기 때문에 JVM은 이것을 가비지 콜렉터(GC)를 실행시켜 자동으로 제거한다.</li>
<li>쓰레드가 몇 개가 존재하든, 단 하나의 영역만 존재한다. 그래서 동기화 이슈가 발생할 가능성도 있다.</li>
</ul>
<br />
<br />

<h2 id="🧾-stack-영역">🧾 Stack 영역</h2>
<ul>
<li>primitive 타입의 데이터(int, double, byte, long, boolean 등)에 해당되는 지역변수, 매개 변수 데이터 값이 저장되는 영역이다.</li>
<li>변수는 메서드 호출 시 스택 프레임 내에 생성되며, 초기화되지 않으면 사용 불가하다.</li>
<li>Heap 영역에 생성된 데이터의 참조값이 할당됨.</li>
<li>메소드가 호출될 때 메모리에 할당, 메서드 종료시 메모리에서 삭제됨.</li>
<li>자료구조 Stack의 구조이다, LIFO(Last In First Out).</li>
<li>각 스레드마다 자신만의 Stack을 가진다. (1:1) - (Thread:Stack). 각 Thread는 독립적인 Stack을 가지며, Heap과 Static 영역은 모든 Thread가 공유한다. 그러므로 Thread는 다른 Thread에 접근할 수 없지만, Static, Heap 영역을 공유하여 사용 가능.</li>
</ul>
<br />
<br />


<blockquote>
<p><strong>참조</strong></p>
</blockquote>
<ul>
<li><a href="https://lucas-owner.tistory.com/38">https://lucas-owner.tistory.com/38</a></li>
<li><a href="https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EB%B3%B4%EB%8A%94-%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%98%81%EC%97%AD%EC%8A%A4%ED%83%9D-%ED%9E%99">https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EB%B3%B4%EB%8A%94-%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%98%81%EC%97%AD%EC%8A%A4%ED%83%9D-%ED%9E%99</a></li>
<li>혼자서 공부하는 자바</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 15686번 치킨 배달]]></title>
            <link>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-15686%EB%B2%88-%EC%B9%98%ED%82%A8-%EB%B0%B0%EB%8B%AC</link>
            <guid>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-15686%EB%B2%88-%EC%B9%98%ED%82%A8-%EB%B0%B0%EB%8B%AC</guid>
            <pubDate>Wed, 14 May 2025 14:50:19 GMT</pubDate>
            <description><![CDATA[<p>이번에는 치킨 배달이라는 문제를 풀어보았다. 오랜만에 백트래킹 문제를 풀면서 감을 살리기 위해 골드급 문제도 도전을 해보았다. 거의 근접하게 풀었는데 너무 어렵게 생각하였고 문제의 내용이 헷갈려서 조금 해맸던 문제이다.</p>
<br />

<h2 id="🔥-문제">🔥 문제</h2>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/15686">https://www.acmicpc.net/problem/15686</a></p>
</blockquote>
<p>크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸, 왼쪽에서부터 c번째 칸을 의미한다. r과 c는 1부터 시작한다.</p>
<p>이 도시에 사는 사람들은 치킨을 매우 좋아한다. 따라서, 사람들은 &quot;치킨 거리&quot;라는 말을 주로 사용한다. 치킨 거리는 집과 가장 가까운 치킨집 사이의 거리이다. 즉, 치킨 거리는 집을 기준으로 정해지며, 각각의 집은 치킨 거리를 가지고 있다. 도시의 치킨 거리는 모든 집의 치킨 거리의 합이다.</p>
<p>임의의 두 칸 (r1, c1)과 (r2, c2) 사이의 거리는 |r1 - r2| + |c1 - c2|로 구한다.</p>
<p>예를 들어, 아래와 같은 지도를 갖는 도시를 살펴보자.</p>
<p>0 2 0 1 0
1 0 1 0 0
0 0 0 0 0
0 0 0 1 1
0 0 0 1 2</p>
<p>0은 빈 칸, 1은 집, 2는 치킨집이다.</p>
<p>(2, 1)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |2 - 1| + |1 - 2| = 2, (5, 5)에 있는 치킨집과의 거리는 |2 - 5| + |1 - 5| = 7이다. 따라서, (2, 1)에 있는 집의 치킨 거리는 2이다.</p>
<p>(5, 4)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |5 - 1| + |4 - 2| = 6, (5, 5)에 있는 치킨집과의 거리는 |5 - 5| + |4 - 5| = 1이다. 따라서, (5, 4)에 있는 집의 치킨 거리는 1이다.</p>
<p>이 도시에 있는 치킨집은 모두 같은 프랜차이즈이다. 프렌차이즈 본사에서는 수익을 증가시키기 위해 일부 치킨집을 폐업시키려고 한다. 오랜 연구 끝에 이 도시에서 가장 수익을 많이 낼 수 있는  치킨집의 개수는 최대 M개라는 사실을 알아내었다.</p>
<p>도시에 있는 치킨집 중에서 최대 M개를 고르고, 나머지 치킨집은 모두 폐업시켜야 한다. 어떻게 고르면, 도시의 치킨 거리가 가장 작게 될지 구하는 프로그램을 작성하시오.</p>
<h2 id="입력">입력</h2>
<p>첫째 줄에 N(2 ≤ N ≤ 50)과 M(1 ≤ M ≤ 13)이 주어진다.</p>
<p>둘째 줄부터 N개의 줄에는 도시의 정보가 주어진다.</p>
<p>도시의 정보는 0, 1, 2로 이루어져 있고, 0은 빈 칸, 1은 집, 2는 치킨집을 의미한다. 집의 개수는 2N개를 넘지 않으며, 적어도 1개는 존재한다. 치킨집의 개수는 M보다 크거나 같고, 13보다 작거나 같다.</p>
<h2 id="출력">출력</h2>
<p>첫째 줄에 폐업시키지 않을 치킨집을 최대 M개를 골랐을 때, 도시의 치킨 거리의 최솟값을 출력한다.</p>
<br />


<h2 id="✍️-문제-풀이">✍️ 문제 풀이</h2>
<p>이 문제는 처음에 치킨집의 위치를 ArrayList에 담은 후 백트래킹을 이용하여 graph 배열에 표시한 후 dfs로 한 번 더 탐색하여 각각의 1에서의 거리를 측정하는 방식으로 구현하였지만 너무 복잡하기도 했고 시간 성능 면에서도 좋지 않아서 어떻게 할지 많이 해맸었다.</p>
<p>그래서 dfs를 한 번만 사용하는 로직으로 작성해 보았다.</p>
<h3 id="처음-작성한-코드">처음 작성한 코드</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;


class Main {
    static int N;
    static int M;

    // 그래프
    static int[][] graph;
    static int[][] visited;

    // 1의 좌표를 저장할 ArrayList
    static ArrayList&lt;int[]&gt; houseLocation = new ArrayList&lt;&gt;();

    // 2의 좌표를 저장할 ArrayList
    static ArrayList&lt;int[]&gt; chickenLocation = new ArrayList&lt;&gt;();

    static boolean[] chickenVisited;

    static int result = Integer.MAX_VALUE;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer info = new StringTokenizer(br.readLine());

        N = Integer.parseInt(info.nextToken());
        M = Integer.parseInt(info.nextToken());

        // 그래프 생성
        graph = new int[N][N];

        for (int i = 0; i &lt; N; i++) {
            StringTokenizer nums = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; N; j++) {
                int num = Integer.parseInt(nums.nextToken());

                if (num != 2) {
                    if (num == 1) houseLocation.add(new int[] {i, j});
                    graph[i][j] = num;
                } else {
                    graph[i][j] = 0;
                    // 2인 값을 저장(치킨집 저장)
                    chickenLocation.add(new int[] {i, j});
                }
            }
        }

        // 최대 M만큼 치킨집 세우기
        for (int i = 1; i &lt; M + 1; i++) {
            chickenVisited = new boolean[chickenLocation.size()];
            dfs(0, i);
        }

        System.out.println(result);
    }

    private static void dfs(int current, int depth) {
        if (current == depth) {
            createVisited();
            int sum = 0;

            for (int i = 0; i &lt; N; i++) {
                for (int j = 0; j &lt; N; j++) {
                    if (graph[i][j] == 2) {
                        for (int[] house : houseLocation) {
                            visited[house[0]][house[1]] = Math.min(visited[house[0]][house[1]], (Math.abs(i - house[0]) + Math.abs(j - house[1])));
                        }
                    }
                }
            }

            for (int[] house: houseLocation) {
                sum += visited[house[0]][house[1]];
            }

            result = Math.min(result, sum);
            return;
        }

        for (int i = current; i &lt; chickenLocation.size(); i++) {
            if (!chickenVisited[i]) {
                int[] location = chickenLocation.get(i);
                graph[location[0]][location[1]] = 2;
                chickenVisited[i] = true;

                dfs(current + 1, depth);

                graph[location[0]][location[1]] = 0;
                chickenVisited[i] = false;
            }
        }

    }


    // 방문 리스트 생성
    private static void createVisited() {
        visited = new int[N][N];
        for (int i = 0; i &lt; N; i++) {
            Arrays.fill(visited[i], 99999);
        }
    }

}</code></pre>
<p>위의 코드처럼 작성을 하니 문제에 주어진 테스트 케이스는 다 올바르게 나왔지만 시간 초과가 발생하였다... 좀 더 생각하다가 도저히 안 떠올라 결국 다른 분들의 코드를 참조하였다. ㅠㅠ</p>
<h3 id="정답-코드">정답 코드</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;


class Main {
    static int N;
    static int M;

    static int[][] graph;

    // 1의 좌표를 저장할 ArrayList
    static ArrayList&lt;int[]&gt; houseLocation = new ArrayList&lt;&gt;();

    // 2의 좌표를 저장할 ArrayList
    static ArrayList&lt;int[]&gt; chickenLocation = new ArrayList&lt;&gt;();

    static boolean[] chickenVisited;

    static int result = Integer.MAX_VALUE;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer info = new StringTokenizer(br.readLine());

        N = Integer.parseInt(info.nextToken());
        M = Integer.parseInt(info.nextToken());

        graph = new int[N][N];

        for (int i = 0; i &lt; N; i++) {
            StringTokenizer nums = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; N; j++) {
                int num = Integer.parseInt(nums.nextToken());
                graph[i][j] = num;

                if (num == 1) {
                    houseLocation.add(new int[] {i, j});
                }

                if (num == 2) {
                    chickenLocation.add(new int[] {i, j});
                }
            }
        }

        chickenVisited = new boolean[chickenLocation.size()];
        dfs(0, 0);

        System.out.println(result);
    }

    private static void dfs(int start, int depth) {
        if (depth == M) {
            int sum = 0;

            // 각 부분별로
            for (int[] house : houseLocation) {
                int min = Integer.MAX_VALUE;
                for (int i = 0; i &lt; chickenLocation.size(); i++) {
                    if (chickenVisited[i]) {
                        int chickenX = chickenLocation.get(i)[0];
                        int chickenY = chickenLocation.get(i)[1];
                        min = Math.min(min, Math.abs(house[0] - chickenX) + Math.abs(house[1] - chickenY));
                    }
                }
                sum += min;
            }

            result = Math.min(result, sum);
            return;
        }

        for (int i = start; i &lt; chickenLocation.size(); i++) {
            if (!chickenVisited[i]) {
                chickenVisited[i] = true;
                dfs(i + 1, depth + 1);
                chickenVisited[i] = false;
            }
        }

    }
}</code></pre>
<p>문제에서 최대 M개를 고른다는 부분에서 잘못 이해하여 M개 이하로 선택하는 모든 경우를 의미한다고 생각하여 저장한 치킨집의 좌표들을 M개 이하의 조합으로 백트래킹을 진행했었다. 하지만 그게 아니었다. M개를 고른다는 의미였다... 또한 거리를 dfs로 구하려고 했던 것도 알고 보면 1의 좌표 즉, 집의 좌표도 치킨집의 좌표를 저장한 것처럼 저장한 후 백트래킹에서 return 하는 부분에서 맨해튼 거리로 계산하면 되는 문제였다. </p>
<p>이 2가지를 이해하고 나니 정말 간단한 코드로 구현할 수 있었다. 결론은 문제를 잘 이해하고 좀 복잡하거나 더러운 코드가 나온다면 시간 복잡도를 잘 생각해서 다른 아이디어로 생각해야 할 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 15665번 N과 M (11)]]></title>
            <link>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-15665%EB%B2%88-N%EA%B3%BC-M-11</link>
            <guid>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-15665%EB%B2%88-N%EA%B3%BC-M-11</guid>
            <pubDate>Wed, 14 May 2025 14:27:31 GMT</pubDate>
            <description><![CDATA[<p>오늘은 오랜만에 백트래킹 문제를 풀어보았다. 백준에서 백트래킹으로 가장 기본적인 문제 시리즈인 N과 M의 11번째 문제를 풀어보았다. 오랜만에 백트래킹을 풀어서인지 실버 문제였는데도 조금 오래 걸렸다.</p>
<br />

<h2 id="🔥-문제">🔥 문제</h2>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/15665">https://www.acmicpc.net/problem/15665</a></p>
</blockquote>
<p>N개의 자연수와 자연수 M이 주어졌을 때, 아래 조건을 만족하는 길이가 M인 수열을 모두 구하는 프로그램을 작성하시오.</p>
<ul>
<li>N개의 자연수 중에서 M개를 고른 수열</li>
<li>같은 수를 여러 번 골라도 된다.</li>
</ul>
<h2 id="입력">입력</h2>
<p>첫째 줄에 N과 M이 주어진다. (1 ≤ M ≤ N ≤ 7)</p>
<p>둘째 줄에 N개의 수가 주어진다. 입력으로 주어지는 수는 10,000보다 작거나 같은 자연수이다.</p>
<h2 id="출력">출력</h2>
<p>한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다.</p>
<p>수열은 사전 순으로 증가하는 순서로 출력해야 한다.</p>
<br />

<h2 id="문제-풀이">문제 풀이</h2>
<p>테스트 케이스를 보니 주어진 N개의 수 중에서 중복된 값이 주어지는 경우가 있어서 이 부분에 대해서 처음부터 중복 제거를 하고 가는 것이 이후 로직을 짜는데 편하겠다 싶어서 ArrayList를 생성하여 숫자들을 담으면서 중복을 제거해주었다. 그리고 사전 순으로 출력하라고 하였으니 미리 중복 제거한 숫자들을 오름차순 정렬을 해주었다. 그 후 M의 길이만큼 백트래킹을 진행해주니 올바른 답이 도출될 수 있었다. 여기서 주의할 점은 같은 수를 여러 번 골라도 된다고 하였으므로 방문처리는 따로 해주지 않았다.</p>
<p>그런데 처음에는 출력 부분을 <code>StringBuilder</code>로 출력하지 않고 <code>System.out.print()</code>로 출력을 하니 시간초과가 발생하였다. 그래서 어떤 부분이 문제인지 잘 몰랐었는데 <strong>파타곤이야</strong> 형(알고리즘 고수라서 많이 배우고 있는 형이다 ㅎㅎ)이 출력 부분에서 시간초과가 발생했을테니 StringBuilder로 출력해보라고 해서 시도했는데 놀랍게도 통과가 되었다. 이 부분에 대해서 원인을 알기 위해서 GPT한테 물어보니 <code>System.out.print()</code>를 많이 호출하게 되면 입출력 병목이 발생하게 되어 시간 초과가 발생할 수 있다고 하였다. 앞으로는 이 부분을 잘 인지하여 알고리즘 문제를 풀어야겠다....</p>
<br />

<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;


class Main {
    static int N;
    static int M;

    static ArrayList&lt;Integer&gt; arr = new ArrayList&lt;&gt;();
    static int[] visited;

    static StringBuilder result = new StringBuilder();

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer info = new StringTokenizer(br.readLine());

        N = Integer.parseInt(info.nextToken());
        M = Integer.parseInt(info.nextToken());

        // 숫자들을 담으면서 중복 제거하기
        StringTokenizer nums = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; N; i++) {
            int num = Integer.parseInt(nums.nextToken());

            if (!arr.contains(num)) {
                arr.add(num);
            }
        }

        // 정렬하기 - 오름차순
        Collections.sort(arr);

        // 방문 리스트 생성
        visited = new int[M];

        // 담은 후 dfs로 길이만큼 조합하기 - 길이는 M만큼이다.
        dfs(0);

        System.out.println(result.toString());
    }

    private static void dfs(int depth) {
        if (depth == M) {
            for (int i = 0; i &lt; M; i++) {
                result.append(visited[i]).append(&quot; &quot;);
            }
            result.append(&quot;\n&quot;);
            return;
        }

        for (int i = 0; i &lt; arr.size(); i++) {
            visited[depth] = arr.get(i);
            dfs(depth + 1);
        }
    }

}</code></pre>
<blockquote>
<p>파타곤이야 블로그 주소 <a href="https://velog.io/@yg9618/posts">https://velog.io/@yg9618/posts</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 1967번 트리의 지름]]></title>
            <link>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-1967%EB%B2%88-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84</link>
            <guid>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-1967%EB%B2%88-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84</guid>
            <pubDate>Tue, 13 May 2025 09:38:14 GMT</pubDate>
            <description><![CDATA[<p>오늘은 트리의 지름이라는 문제를 풀어보았다. 처음에 풀었던 방식으로는 메모리 초과가 발생해서 결국은 다른 분들의 해설을 참고해서 풀었다. 참고한 방식이 좋은 문제라서 가져왔다.</p>
<br />

<h2 id="🔥-문제">🔥 문제</h2>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/1967">https://www.acmicpc.net/problem/1967</a></p>
</blockquote>
<p>트리(tree)는 사이클이 없는 무방향 그래프이다. 트리에서는 어떤 두 노드를 선택해도 둘 사이에 경로가 항상 하나만 존재하게 된다. 트리에서 어떤 두 노드를 선택해서 양쪽으로 쫙 당길 때, 가장 길게 늘어나는 경우가 있을 것이다. 이럴 때 트리의 모든 노드들은 이 두 노드를 지름의 끝 점으로 하는 원 안에 들어가게 된다.</p>
<p>
    <img src='https://www.acmicpc.net/JudgeOnline/upload/201007/ttrrtrtr.png' width='50%' height='50%' />
</p>


<p>이런 두 노드 사이의 경로의 길이를 트리의 지름이라고 한다. 정확히 정의하자면 트리에 존재하는 모든 경로들 중에서 가장 긴 것의 길이를 말한다.</p>
<p>입력으로 루트가 있는 트리를 가중치가 있는 간선들로 줄 때, 트리의 지름을 구해서 출력하는 프로그램을 작성하시오. 아래와 같은 트리가 주어진다면 트리의 지름은 45가 된다.</p>
<p>
    <img src='https://www.acmicpc.net/JudgeOnline/upload/201007/tttttt.png' width='50%' height='50%' />
</p>

<p>트리의 노드는 1부터 n까지 번호가 매겨져 있다.</p>
<h2 id="입력">입력</h2>
<p>파일의 첫 번째 줄은 노드의 개수 n(1 ≤ n ≤ 10,000)이다. 둘째 줄부터 n-1개의 줄에 각 간선에 대한 정보가 들어온다. 간선에 대한 정보는 세 개의 정수로 이루어져 있다. 첫 번째 정수는 간선이 연결하는 두 노드 중 부모 노드의 번호를 나타내고, 두 번째 정수는 자식 노드를, 세 번째 정수는 간선의 가중치를 나타낸다. 간선에 대한 정보는 부모 노드의 번호가 작은 것이 먼저 입력되고, 부모 노드의 번호가 같으면 자식 노드의 번호가 작은 것이 먼저 입력된다. 루트 노드의 번호는 항상 1이라고 가정하며, 간선의 가중치는 100보다 크지 않은 양의 정수이다.</p>
<h2 id="출력">출력</h2>
<p>첫째 줄에 트리의 지름을 출력한다.</p>
<br />


<h2 id="문제-풀이">문제 풀이</h2>
<p>처음 풀었을 때에는 모든 노드를 짝지어서 dfs를 돌린 다음 최대 가중치를 갱신해 주는 방법으로 구현하였는데 메모리 초과가 발생하였다. </p>
<p>아래의 코드처럼 작성하게 된다면 visited 배열이 수천만 번 생성이 되므로 메모리 제한이 128MB인 이 문제에서는 메모리 초과가 발생되었던 것이다. 그래서 이 코드로는 해결이 불가했던 것이다.(GC가 기존 visited 배열을 지워주지만 너무 많이 생성하면 GC의 제거보다 생성이 더 많아서 결국은 GC가 따라가지 못하여 메모리 초과가 발생한다.) </p>
<h3 id="처음에-작성한-코드">처음에 작성한 코드</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;


class Main {

    static int N;

    static ArrayList&lt;ArrayList&lt;int[]&gt;&gt; graph = new ArrayList&lt;&gt;();
    static boolean[] visited;

    static int result = 0;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        N = Integer.parseInt(br.readLine());

        // 그래프 생성
        for (int i = 0; i &lt; N + 1; i++) {
            graph.add(new ArrayList&lt;&gt;());
        }

        // 간선 연결 - 가중치랑 같이
        for (int i = 0; i &lt; N - 1; i++) {
            StringTokenizer link = new StringTokenizer(br.readLine());
            int s = Integer.parseInt(link.nextToken());
            int e = Integer.parseInt(link.nextToken());
            int weight = Integer.parseInt(link.nextToken());

            graph.get(s).add(new int[] {e, weight});
            graph.get(e).add(new int[] {s, weight});
        }

        // 2중 for문으로 dfs 돌리기
        for (int i = 1; i &lt; N; i++) {
            for (int j = i + 1; j &lt; N + 1; j++) {
                // 방문 리스트 생성
                visited = new boolean[N + 1];
                dfs(i, j, 0);
            }
        }

        System.out.println(result);

    }

    private static void dfs(int start, int goal, int count) {
        visited[start] = true;

        if (start == goal) {
            result = Math.max(result, count);
            return;
        }

        for (int[] next : graph.get(start)) {
            int node = next[0];
            int weight = next[1];

            if (!visited[node]) {
                dfs(node, goal, count + weight);
            }

        }
    }
}</code></pre>
<p>그래서 다른 분들의 해설을 참조해 보니 <strong>처음 루트 노드에서 dfs 탐색을 통해 가장 가중치가 큰 노드를 찾은 다음 그 노드에서 다시 한번 dfs 탐색을 이용하여 가장 가중치가 큰 경우를 찾는 방법</strong>을 사용하면 시간과 메모리 측면에서 매우 효율적인 코드를 작성할 수 있다는 것을 알게 되었다.</p>
<p>아래는 정답 코드이다.</p>
<h3 id="정답-코드">정답 코드</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;


class Main {
    static int N;

    static ArrayList&lt;ArrayList&lt;int[]&gt;&gt; graph = new ArrayList&lt;&gt;();
    static boolean[] visited;

    static int maxNode = 0;
    static int result = 0;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        N = Integer.parseInt(br.readLine());

        // 그래프 생성
        for (int i = 0; i &lt; N + 1; i++) {
            graph.add(new ArrayList&lt;&gt;());
        }

        // 간선 연결 - 가중치랑 같이
        for (int i = 0; i &lt; N - 1; i++) {
            StringTokenizer link = new StringTokenizer(br.readLine());
            int s = Integer.parseInt(link.nextToken());
            int e = Integer.parseInt(link.nextToken());
            int weight = Integer.parseInt(link.nextToken());

            graph.get(s).add(new int[] {e, weight});
            graph.get(e).add(new int[] {s, weight});
        }

        // 루트 노드에서 가장 큰 가중치를 가진 maxNode를 찾은 후 maxNode에서 가중치가 가장 큰 노드를 찾기
        visited = new boolean[N + 1];
        dfs(1, 0);

        visited = new boolean[N + 1];
        dfs(maxNode, 0);


        System.out.println(result);
    }

    private static void dfs(int node,  int count) {
        visited[node] = true;
        if (count &gt; result) {
            result = Math.max(result, count);
            maxNode = node;
        }

        for (int[] next : graph.get(node)) {
            int n = next[0];
            int weight = next[1];

            if (!visited[n]) {
                dfs(n,count + weight);
            }

        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 1068번 트리 java]]></title>
            <link>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-1068%EB%B2%88-%ED%8A%B8%EB%A6%AC-java</link>
            <guid>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-1068%EB%B2%88-%ED%8A%B8%EB%A6%AC-java</guid>
            <pubDate>Fri, 02 May 2025 11:26:36 GMT</pubDate>
            <description><![CDATA[<p>오늘은 2일전에 풀었던 트리라는 문제를 다시 한번 풀어보았다. 이 문제는 설명이 친절하지 않아서 많은 혼란을 주었던 문제이다.</p>
<br />

<h2 id="🔥-문제">🔥 문제</h2>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/1068">https://www.acmicpc.net/problem/1068</a></p>
</blockquote>
<p>트리에서 리프 노드란, 자식의 개수가 0인 노드를 말한다.</p>
<p>트리가 주어졌을 때, 노드 하나를 지울 것이다. 그 때, 남은 트리에서 리프 노드의 개수를 구하는 프로그램을 작성하시오. 노드를 지우면 그 노드와 노드의 모든 자손이 트리에서 제거된다.</p>
<p>예를 들어, 다음과 같은 트리가 있다고 하자.</p>
<p>
    <img src='https://velog.velcdn.com/images/hk_98/post/0b1e697a-7f2a-4f99-a770-0008cb5e9866/image.png' width='30%' height='30%' />
</p>


<p>현재 리프 노드의 개수는 3개이다. (초록색 색칠된 노드) 이때, 1번을 지우면, 다음과 같이 변한다. 검정색으로 색칠된 노드가 트리에서 제거된 노드이다.</p>
<p>
    <img src='https://velog.velcdn.com/images/hk_98/post/5a15275b-4758-43a2-8c97-0997718e9432/image.png' width='30%' height='30%' />
</p>

<p>이제 리프 노드의 개수는 1개이다.</p>
<br />

<h2 id="입력">입력</h2>
<p>첫째 줄에 트리의 노드의 개수 N이 주어진다. N은 50보다 작거나 같은 자연수이다. 둘째 줄에는 0번 노드부터 N-1번 노드까지, 각 노드의 부모가 주어진다. 만약 부모가 없다면 (루트) -1이 주어진다. 셋째 줄에는 지울 노드의 번호가 주어진다.</p>
<br />

<h2 id="출력">출력</h2>
<p>첫째 줄에 입력으로 주어진 트리에서 입력으로 주어진 노드를 지웠을 때, 리프 노드의 개수를 출력한다.</p>
<br />


<h2 id="문제-풀이">문제 풀이</h2>
<p>이 문제는 dfs 탐색을 통해 해결하였다.</p>
<p>우선 그래프를 생성하고 부모 자식 간에 연결을 하였다. 단, 루트 노드일 경우는 따로 루트 노드를 저장하였다. 처음에는 루트가 무조건 0인 줄 알고 따로 저장하지 않고 dfs 함수에 0을 하드코딩으로 넣었는데 이 점을 주의해야 한다. </p>
<p>그리고 방문 리스트는 따로 생성하지 않았는데 이유는 트리여서 단방향 dfs이므로 굳이 방문 체크하지 않아도 모두 탐색할 것이기 때문이다. 이렇게 그래프를 생성하고 연결한 후 제거할 루트 노드 값을 받고 제거를 하였다. </p>
<p>제거할 때는 한곳만 끊어버리면 나머지 아래쪽은 탐색을 하지 않을 것이기 때문에 굳이 자식에 자식까지 처리를 해주지 않았다.</p>
<p>그래서 최종적으로 아래와 같은 코드로 문제를 해결하였다.</p>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;

class Main {

    static int N;
    static ArrayList&lt;ArrayList&lt;Integer&gt;&gt; graph = new ArrayList&lt;&gt;();
    static boolean[] visited;

    // 루트 노드
    static int root = 0;

    // 제거해야할 노드
    static int removeNode;
    static int result = 0;
    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        N = Integer.parseInt(br.readLine());
        visited = new boolean[N];

        for (int i = 0; i &lt; N; i++) {
            graph.add(new ArrayList&lt;&gt;());
        }

        // 그래프 연결
        StringTokenizer edge = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; N; i++) {
            int parent = Integer.parseInt(edge.nextToken());
            if (parent != -1) {
                graph.get(parent).add(i);
            } else {
                root = i;
            }
        }

        removeNode = Integer.parseInt(br.readLine());
        if (removeNode == root) {
            System.out.println(0);
            return;
        }

        // 노드 제거
        for (int i = 0; i &lt; N; i++) {
            graph.get(i).remove(Integer.valueOf(removeNode));
        }

        dfs(root);
        System.out.println(result);
    }

    private static void dfs(int node) {

        // 자식이 없으면 리프노드이므로 결과에 +1처리 후 return
        if (graph.get(node).isEmpty()) {
            result++;
            return;
        }

        for (int next : graph.get(node)) {
            dfs(next);
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 7576번 토마토 java]]></title>
            <link>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-7576%EB%B2%88-%ED%86%A0%EB%A7%88%ED%86%A0-java</link>
            <guid>https://velog.io/@hk_98/%EB%B0%B1%EC%A4%80-7576%EB%B2%88-%ED%86%A0%EB%A7%88%ED%86%A0-java</guid>
            <pubDate>Thu, 01 May 2025 12:06:30 GMT</pubDate>
            <description><![CDATA[<p>요즘 DFS 및 BFS 유형의 문제들을 풀고 있는데 오늘은 백준 7576번 토마토라는 문제를 풀어보았다.</p>
<br />

<h2 id="🔥-문제">🔥 문제</h2>
<blockquote>
<p><a href="https://www.acmicpc.net/problem/7576">https://www.acmicpc.net/problem/7576</a></p>
</blockquote>
<p>철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자 모양 상자의 칸에 하나씩 넣어서 창고에 보관한다.</p>
<p align='center'>
  <img src="https://velog.velcdn.com/images/hk_98/post/0d9402ff-521b-4e56-a6ef-0f6bdd03777c/image.png" width="30%" height="30%">
</p>

<p>창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다. 보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다. 하나의 토마토의 인접한 곳은 왼쪽, 오른쪽, 앞, 뒤 네 방향에 있는 토마토를 의미한다. 대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다. 철수는 창고에 보관된 토마토들이 며칠이 지나면 다 익게 되는지, 그 최소 일수를 알고 싶어 한다.</p>
<p>토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.</p>
<br />

<h2 id="입력">입력</h2>
<p>첫 줄에는 상자의 크기를 나타내는 두 정수 M,N이 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M,N ≤ 1,000 이다. 둘째 줄부터는 하나의 상자에 저장된 토마토들의 정보가 주어진다. 즉, 둘째 줄부터 N개의 줄에는 상자에 담긴 토마토의 정보가 주어진다. 하나의 줄에는 상자 가로줄에 들어있는 토마토의 상태가 M개의 정수로 주어진다. 정수 1은 익은 토마토, 정수 0은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않은 칸을 나타낸다.</p>
<p>토마토가 하나 이상 있는 경우만 입력으로 주어진다.</p>
<br />

<h2 id="출력">출력</h2>
<p>여러분은 토마토가 모두 익을 때까지의 최소 날짜를 출력해야 한다. 만약, 저장될 때부터 모든 토마토가 익어있는 상태이면 0을 출력해야 하고, 토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다.</p>
<br />

<h2 id="문제-풀이">문제 풀이</h2>
<p>이러한 문제에서 나는 토마토가 모두 익는 최소 일수를 구하는 문제여서 BFS를 통해서 풀 수 있다고 생각하여 BFS를 사용하여 문제를 해결하였다.</p>
<p>처음부터 이미 토마토가 모두 익어있는 상황과 BFS를 돌리고 난 다음에 모두 익지 않은 상황을 나누어서 조건을 분기하였다.</p>
<p>이미 토마토가 모두 익어있는 상황은 isRipe 변수를 만들어 그래프를 이중 for문으로 탐색하면서 체크하였고 BFS를 처리한 다음에 모두 익지 않은 상황은 최종적으로 결과를 도출하면서 아직 방문 처리하지 않은 토마토가 있으면 바로 -1을 출력하는 방식으로 처리하였다.</p>
<p>BFS를 구현하면서 처음에는 Queue를 bfs 함수 내에 구현하여 진행해 보았는데 이렇게 구현하면 처음에 익은 토마토가 2개 이상일 경우에는 답이 다르게 나온다. 즉, 내부에 Queue를 구현하면 스타팅 포인트를 1개만 지정할 수 있다는 의미이다. 그래서 처음부터 익은 토마토 여러 개를 처리하기 위하여 static 변수에 Queue를 생성하여 익은 토마토를 넣은 후 bfs 함수를 구현하였다.</p>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

class Main {
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1};

    static int N;
    static int M;

    static int[][] graph;
    static int[][] visited;
    static Queue&lt;int[]&gt; tomatoStart = new LinkedList&lt;&gt;();

    // 토마토가 모두 익어있는지 체크할 변수
    static boolean isRipe = true;
    static int result = 0;
    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer info = new StringTokenizer(br.readLine());

        N = Integer.parseInt(info.nextToken());
        M = Integer.parseInt(info.nextToken());

        graph = new int[M][N];
        visited = new int[M][N];

        // graph에 값을 넣으면서 벽이나 익은 곳에는 visited 배열에 1을 넣기
        for (int i = 0; i &lt; M; i++) {
            StringTokenizer tomatoes = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; N; j++) {
                int tomato = Integer.parseInt(tomatoes.nextToken());
                graph[i][j] = tomato;

                if (tomato == -1 || tomato == 1) {
                    visited[i][j] = 1;

                    // 익은 토마토면 Queue에 넣기
                    if (tomato == 1) {
                        tomatoStart.offer(new int[] {i , j});
                    }
                }

                // 이미 모든 토마토가 익어있는지 체크
                if (tomato == 0) {
                    isRipe = false;
                }
            }
        }

        // 모든 토마토가 익어있으면 0 출력
        if (isRipe) {
            System.out.println(0);
            return;
        }

        bfs();

        // 토마토가 모두 익었는지 체크하면서 최대 일수 체크
        for (int i = 0; i &lt; M; i++) {
            for (int j = 0; j &lt; N; j++) {
                result = Math.max(result, visited[i][j]);

                // 만약 안 익은 부분이 있으면 -1 출력 후 종료
                if (visited[i][j] == 0) {
                    System.out.println(-1);
                    return;
                }
            }
        }

        // 처음 시작을 1일로 했으므로 결과에서 1을 뺐음.
        System.out.println(result - 1);
    }

    private static void bfs() {

        while (!tomatoStart.isEmpty()) {
            int[] tomato = tomatoStart.poll();

            for (int i = 0; i &lt; 4; i++) {
                int nx = tomato[0] + dx[i];
                int ny = tomato[1] + dy[i];

                if ((0 &lt;= nx &amp;&amp; nx &lt; M) &amp;&amp; (0 &lt;= ny &amp;&amp; ny &lt; N) &amp;&amp; visited[nx][ny] == 0 &amp;&amp; graph[nx][ny] == 0) {
                    graph[nx][ny] = 1;
                    visited[nx][ny] = visited[tomato[0]][tomato[1]] + 1;
                    tomatoStart.offer(new int[] {nx, ny});
                }
            }
        }
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[캡슐화(Encapsulation)]]></title>
            <link>https://velog.io/@hk_98/%EC%BA%A1%EC%8A%90%ED%99%94-%EC%83%88%EA%B8%80</link>
            <guid>https://velog.io/@hk_98/%EC%BA%A1%EC%8A%90%ED%99%94-%EC%83%88%EA%B8%80</guid>
            <pubDate>Sat, 26 Apr 2025 15:22:18 GMT</pubDate>
            <description><![CDATA[<p>먼저 캡슐화를 쉽게 이해하기 위해서는 접근 제어자라는 것을 먼저 이해하고 가면 좋다.</p>
<h2 id="🧩-접근-제어자">🧩 접근 제어자</h2>
<p>접근 제어자란 클래스, 메서드, 필드 등 객체 지향 프로그래밍의 구성 요소에 대해 외부에서 접근할 수 있는 범위를 설정하는 키워드이다. 코드의 캡슐화를 지원하고, 데이터 보호와 프로그램의 안정성을 높이는 데 사용된다. </p>
<p>내가 주로 사용하는 자바에서는 네 가지의 접근 제어자를 제공한다. </p>
<br />

<h3 id="🔒-private">🔒 private</h3>
<p><code>private</code> 접근 제어자를 사용하면 같은 클래스 내에서만 접근할 수 있다. 그래서 보통은 멤버 변수에 private을 사용하여 메소드를 통해서만 멤버 변수를 조작하는 방식을 많이 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/9244ec2e-f529-4439-9c79-212cdf2ed172/image.png" alt=""></p>
<p>같은 클래스에서만 접근 허용!</p>
<pre><code class="language-java">public class SameClass {
    private String var = &quot;같은 클래스만 허용&quot;; // private 필드

    private String getVar() {                  // private 메소드
        return this.var;
    }
}
</code></pre>
<br />

<h3 id="🌍-public">🌍 public</h3>
<p><code>public</code> 접근 제어자를 사용하면 해당 객체를 사용하는 프로그램 어디에서나 직접 접근이 가능하다. 주로 외부에서 자유롭게 호출해야 하는 메소드나, 공용 API로 제공되는 필드에 사용된다. 멤버 변수를 직접 public으로 노출하는 것은 권장되지 않으며, 보통은 private 변수에 대한 getter나 setter 메소드를 public으로 제공하는 형태로 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/61adaf5c-5519-47f6-a6ca-1fb6ea467bd3/image.png" alt=""></p>
<p>프로그램 내의 모든 곳에서 누구나 접근 가능!</p>
<pre><code class="language-java">public class Everywhere {
    public String var = &quot;누구든지 허용&quot;; // public 필드

    public String getVar() {             // public 메소드
        return this.var;
    }
}</code></pre>
<br />

<h3 id="🗂️-default">🗂️ default</h3>
<p><code>default</code> 접근 제어자는 명시적으로 접근 제어자를 작성하지 않았을 때 적용되며 같은 패키지 안에 있는 클래스들에서만 접근할 수 있다. 외부 패키지에서는 접근이 불가능하기 때문에 관련된 클래스들끼리만 내부적으로 사용해야 할 때 적합하다. 보통 라이브러리 내부 구현 세부사항이나 패키지 단위로 모듈화된 코드에서 사용된다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/26620bc1-33fd-42f5-b5eb-b6bac55a11d3/image.png" alt=""></p>
<p>같은 패키지 내에서만 접근 허용!</p>
<pre><code class="language-java">package test;

public class SamePackage {
    String sameVar = &quot;같은 패키지는 허용&quot;; // default 필드
}</code></pre>
<p>같은 클래스도 허용!</p>
<pre><code class="language-java">package test;

public class SameClass {
    String var = &quot;다른 패키지는 접근 불가&quot;; // default 필드

    public static void main(String[] args) {
        SamePackage sp = new SamePackage();
        System.out.println(sp.sameVar);     // 같은 패키지는 허용
    }
}</code></pre>
<br />

<h3 id="🛡️-protected">🛡️ protected</h3>
<p><code>protected</code> 접근 제어자는 같은 패키지 안에 있는 클래스들은 물론, 다른 패키지에 있는 상속 받은 자식 클래스에서도 접근할 수 있게 한다. 주로 상속을 통해 기능을 확장할 때, 부모 클래스의 필드나 메소드를 자식 클래스가 접근할 수 있도록 허용하기 위해 사용한다. 다만, protected 멤버는 외부에 노출될 가능성이 있기 때문에 의도하지 않은 접근을 막고 싶다면 private와 함께 신중하게 설계해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/d830d8b9-e069-415f-8faa-f8e2f2aebb32/image.png" alt=""></p>
<p>같은 패키지 허용!</p>
<pre><code class="language-java">
package test;

public class SameClass {
    protected String sameVar = &quot;다른 패키지에 속하는 자식 클래스까지 허용&quot;; // protected 필드
}</code></pre>
<p>다른 패키지에 속하는 자식 클래스도 접근 허용!</p>
<pre><code class="language-java">package test.other;

import test.SameClass; // test 패키지의 SameClass 클래스를 불러들여 포함시킴.

public class ChildClass extends SameClass {

    public static void main(String[] args) {
        SameClass = new SameClass();
        System.out.println(sp.sameVar); // 다른 패키지에 속하는 자식 클래스까지 허용
    }
}</code></pre>
<br />

<table>
<thead>
<tr>
<th align="center">접근 제어자</th>
<th align="center">같은 클래스의 멤버</th>
<th align="center">같은 패키지의 멤버</th>
<th align="center">자식 클래스의 멤버</th>
<th align="center">그 외의 영역</th>
</tr>
</thead>
<tbody><tr>
<td align="center">public</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center">○</td>
</tr>
<tr>
<td align="center">protected</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center">X</td>
</tr>
<tr>
<td align="center">default</td>
<td align="center">○</td>
<td align="center">○</td>
<td align="center">X</td>
<td align="center">X</td>
</tr>
<tr>
<td align="center">private</td>
<td align="center">○</td>
<td align="center">X</td>
<td align="center">X</td>
<td align="center">X</td>
</tr>
</tbody></table>
<br />
<br />
<br />

<p>자 이제 접근 제어자에 대해서 알고 왔으니 캡슐화에 대해서 좀 더 이해하기 쉬울 것이다.</p>
<h2 id="📦-캡슐화란">📦 캡슐화란?</h2>
<p><span style="color:orange"><strong>캡슐화</strong></span>는 객체의 상태(데이터)를 외부로부터 직접 접근하거나 조작하지 못하도록 보호하고 대신 메서드를 통해서만 접근하고 조작하도록 제한하는 객체 지향 프로그래밍의 핵심 원칙이다. 이러한 캡슐화를 통해 코드의 안정성과 유지 보수성을 높일 수 있으며, 불필요한 내부 구현 세부사항을 숨기고 필요한 부분만 외부에 공개할 수 있다. </p>
<p>예를 들어, 접근 제어자를 활용하여 객체의 상태(데이터)나 내부에서 사용하는 기능(메소드) <code>private</code>으로 접근을 제한하고 객체의 상태를 조작하는 메소드를 <code>public</code>으로 공개하는 방식이 캡슐화에 해당한다.</p>
<br />

<p>간단한 코드로 예시를 들어보겠다.</p>
<p>아래는 부적절한 예시로 멤버 변수에 직접 접근하여 조작하는 방식이다. 게임에서 캐릭터의 레벨의 만렙이 10이라고 가정해 보자.</p>
<pre><code class="language-java">
public class Character {

    public String name;
    public int level = 1;

    public Character(String name) {
        this.name = name;
    }

    public void levelUp() {
        if (level &gt;= 10) {
            System.out.println(&quot;더이상 레벨을 증가 시킬 수 없습니다. 최대 레벨입니다.&quot;); 
        } else {
            level += 1;
            System.out.println(&quot;레벨 업!&quot;);
        } 
    }
}
</code></pre>
<pre><code class="language-java">
public class CharacterMain {
    public static void main(String[] args) {
        Character character = new Character(&quot;HG-KR98&quot;);
           character.level = 12;
        character.levelUp(); // &quot;더이상 레벨을 증가 시킬 수 없습니다. 최대 레벨입니다.&quot;
    }
}
</code></pre>
<p>위와 같은 코드에서는 멤버 변수를 외부에서 직접 변경할 수 있기 때문에 최대 레벨을 초과하는 캐릭터가 존재하는 비정상적인 상태가 발생한다. 그러므로 캐릭터의 데이터를 직접 접근하여 조작하지 못하도록 할 필요가 있다.</p>
<pre><code class="language-java">
public class Character {

    private String name;
    private int level = 1;

    public Character(String name) {
        this.name = name;
    }

    public void levelUp() {
        if (level &gt;= 10) {
            System.out.println(&quot;더이상 레벨을 증가 시킬 수 없습니다. 최대 레벨입니다.&quot;); 
        } else {
            level += 1;
            System.out.println(&quot;레벨 업!&quot;);
        } 
    }

    public int getLevel() {
        return level;
    }

}
</code></pre>
<pre><code class="language-java">
public class CharacterMain {
    public static void main(String[] args) {
        Character character = new Character(&quot;HG-KR98&quot;);
           // character.level = 12; // 이렇게 작성하면 private 제어 접근자에 의해서 에러 발생!
        character.levelUp(); // 레벨 업! 
        System.out.println(&quot;현재 레벨: &quot; + character.getLevel()); // 2
    }
}
</code></pre>
<p>그러므로 다시 위와 같은 예시에서 볼 수 있듯이 멤버 변수에 <code>private</code>으로 접근 제한을 설정하여 직접 멤버 변수를 조작하지 못하도록 하고 조작은 <code>public</code>으로 설정된 메소드로 조작하도록 하는 것이 캡슐화이다.</p>
<br />

<h3 id="🧊-장점">🧊 장점</h3>
<ol>
<li><p>정보 은닉 : 내부 구현을 숨기고 오직 공개된 인터페이스에만 접근할 수 있도록 함으로써 객체의 복잡성을 숨긴다.</p>
</li>
<li><p>데이터 보호 : 데이터에 직접 접근하는 것을 제한함으로써 데이터의 무결성을 보호하고 잘못된 사용을 방지한다.</p>
</li>
<li><p>코드 재사용 : 외부 인터페이스는 변경되지 않고 내부 구현을 변경할 수 있기 때문에 코드 재사용이 쉽다.</p>
</li>
<li><p>시스템 유지보수성 향상 : 내부 구현을 변경하여도 외부 인터페이스는 변경되지 않기 때문에 시스템의 유지보수가 쉽다.</p>
</li>
</ol>
<br />

<h3 id="🧃-단점">🧃 단점</h3>
<ol>
<li><p>복잡성 증가 : 캡슐화는 코드의 복잡성을 증가시킬 수 있다.</p>
</li>
<li><p>성능 저하 : getter와 setter 메서드를 통해 데이터에 접근하려면 메서드 호출이 필요하므로 일부 성능 손실이 발생할 수 있다.</p>
</li>
<li><p>유연성 감소 : 캡슐화는 데이터와 그에 대한 연산을 하나로 묶지만, 때로는 데이터를 직접 조작하는 것이 필요할 경우도 있다.</p>
</li>
<li><p>접근 제한자 관련 문제 : 적절한 접근 제한자를 선택하지 않으면 캡슐화의 이점을 잃을 수 있다. 일부 개발자는 데이터에 대한 직접적인 접근을 허용하는 접근 제한자를 사용할 수 있으며, 이는 캡슐화의 이점을 상쇄시킬 수 있다.</p>
</li>
<li><p>리팩토링의 어려움 : 캡슐화된 코드를 변경하려면 getter와 setter 메서드의 시그니처를 변경해야 할 수 있다. 이 경우, 해당 클래스를 사용하는 다른 코드에도 변경 사항을 반영해야 할 수 있다.</p>
</li>
</ol>
<br />

<blockquote>
<p><strong>참고</strong>
<a href="https://www.tcpschool.com/java/java_modifier_accessModifier">https://www.tcpschool.com/java/java_modifier_accessModifier</a>
김영한의 실전 자바 - 기본편</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🧩 추상 클래스]]></title>
            <link>https://velog.io/@hk_98/%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@hk_98/%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Sat, 18 Jan 2025 12:48:32 GMT</pubDate>
            <description><![CDATA[<h2 id="🧐-추상-클래스란">🧐 추상 클래스란?</h2>
<p><span style="color:orange"><strong>추상클래스</strong></span>는 여러 클래스 간에 공통된 필드와 메서드를 추출하여 정의한 클래스이다.</p>
<p>예를 들어, 우리가 Phone이라는 클래스를 만든다고 가정해 보자. 이를 기반으로 SmartPhone과 TelePhone이라는 클래스를 만들 수 있다. SmartPhone과 TelePhone은 각기 다른 방식으로 동작하지만, 둘 다 &quot;전화&quot;를 한다는 공통점이 있다. 또한, 전화를 걸기 위해 전화번호를 입력할 때 다이얼을 누른다는 공통된 동작이 존재한다. 다만, SmartPhone은 터치 방식으로 다이얼을 입력하고, TelePhone은 버튼 방식으로 다이얼을 입력한다는 차이가 있다.</p>
<p>이처럼 공통된 특성(예: &quot;다이얼&quot;과 &quot;전화 기능&quot;)을 추출하여 정의한 클래스를 추상 클래스라고 한다. (클래스에서의 공통된 특성은 필드와 메소드를 말한다.) 추상 클래스는 실제로 객체를 생성할 수는 없지만, 이를 기반으로 만들어진 클래스(SmartPhone, TelePhone)에서 구체적인 동작을 구현할 수 있다. 이렇게 추상 클래스를 상속받아 구체적인 객체를 생성하는 클래스를 실체 클래스라고 한다.</p>
<p>이렇게 추상 클래스를 사용하면 코드의 중복을 줄이고, 객체지향 프로그래밍의 일관성을 유지할 수 있다.</p>
<br />
<br />

<h2 id="📋-추상-클래스의-용도">📋 추상 클래스의 용도</h2>
<p>이렇게 실체 클래스의 공통적인 특성(필드, 메소드)을 뽑아내어 추상 클래스로 만드는 이유가 무엇일까?</p>
<h3 id="span-stylecolorskyblue공통된-필드와-메소드의-이름을-통일할-목적span"><span style="color:skyblue">공통된 필드와 메소드의 이름을 통일할 목적</span></h3>
<p>만약 실체 클래스를 설계하는 사람이 여러 명일 경우, 실체 클래스마다 필드와 메소드가 제각기 다른 이름을 가지게 될 것이다.</p>
<p>예를 들어, Telephone에서는 소유자의 이름을 저장하는 필드를 user, SmartPhone에서는 member라고 정의하고 전화를 거는 메소드를 TelePhone에서는 makeCall(), SmartPhone에서는 ring()이라고 한다면 개발을 하면서 개발자마다 쓰는 필드명과 메소드명이 다르기 때문에 혼동이 쉽게 발생하게 된다. </p>
<p>이러한 것을 방지하기 위해 추상 클래스를 사용하는 것이다. 앞선 예시에서 적용하자면 추상 클래스에 user 필드와 makeCall() 메소드를 공통적으로 선언하여 추상 클래스를 구축하면 된다. 추상 클래스에서는 미리 정의한 필드와 메서드가 있다면, 실체 클래스에서 추상 클래스의 필드와 메서드명을 변경할 수 없고 무조건 해당 명명으로 구현해야 한다. 따라서, 필드와 메서드 이름을 통일하여 유지 보수성을 높이고 통일성을 유지할 수 있게 된다.</p>
<h3 id="span-stylecolorskyblue실체-클래스를-작성할-때-시간-절약span"><span style="color:skyblue">실체 클래스를 작성할 때 시간 절약</span></h3>
<p>공통적인 필드와 메소드는 추상 클래스에 모두 선언하고, 다른 특성 및 추가된 특성만 실체 클래스에 선언하면 실체 클래스를 작성하는 데 시간을 절약할 수 있다.</p>
<p>앞의 예시에서 TelePhone과 SmartPhone은 Phone을 상속받기 때문에 user 필드와 makeCall() 메소드를 선언할 필요가 없다. 추가적인 특성만 있을 때 필드 및 메소드를 실체 클래스에 각각 선언을 하면 된다. 그리고 추상 클래스에 추상 메소드라는 것이 있는데 추상 메소드를 구현하기 위한 방법으로는 추상 클래스를 상속받은 실체 클래스에 오버라이딩하여 메소드 내부의 로직을 작성을 하면 된다.</p>
<p>또한 현업에서는 주로 관리자 급 개발자들이 구조를 잡아주고 개발을 하는데 이러한 상황에서도 공통된 부분이 있다면 추상 클래스를 사용할 수 있을 것이다.</p>
<br />
<br />

<h2 id="🖋️-추상-클래스-선언">🖋️ 추상 클래스 선언</h2>
<p>추상 클래스를 선언할 때에는 클래스 선언에 abstract 키워드를 붙여야 한다. abstract를 붙이면 new 연산자를 이용해서 객체를 만들지 못하고 상속을 통해서 자식 클래스를 이용하여 클래스를 만들 수 있다.</p>
<pre><code class="language-java">
public abstract class 클래스 {
  //필드

  //생성자

  //메소드

}
</code></pre>
<p>추상 클래스도 일반 클래스와 마찬가지로 필드, 생성자, 메소드를 선언할 수 있다. new 연산자로 직접 생성자를 호출할 수는 없지만 자식 객체가 생성될 때 super()를 호출해서 추상 클래스 객체를 생성하기에 추상 클래스도 생성자가 반드시 있어야 한다.</p>
<h4 id="예시-코드">예시 코드</h4>
<pre><code class="language-java">package abstractclass;

public abstract class Phone {

    // 필드
    public String user;

    // 생성자
    public Phone(String user) {
        this.user = user;
    }

    // 메소드
    public void makeCall() {
        System.out.println(&quot;전화를 건다&quot;);
    }


}
</code></pre>
<pre><code class="language-java">package abstractclass;

public class SmartPhone extends Phone {
    // 생성자
    public SmartPhone(String user) {
        super(user);
    }

    // 메소드
    public void internetSearch() {
        System.out.println(&quot;검색 하기&quot;);
    }
}
</code></pre>
<pre><code class="language-java">package abstractclass;

public class TelePhone extends Phone {

    public TelePhone(String user) {
        super(user);
    }

    // 메소드
    public void clickDial() {
        System.out.println(&quot;다이얼을 누른다&quot;);
    }

}</code></pre>
<br />

<h2 id="🔄-추상-메소드와-재정의">🔄 추상 메소드와 재정의</h2>
<p>앞에서 잠깐 추상 메소드라는 것을 설명하였는데 더 자세히 설명하자면 추상 메소드는  <strong>객체 지향 프로그래밍(OOP)</strong>에서 메서드의 선언만 정의하고, 구현(실제 동작)은 하지 않은 메서드를 의미한다. 구현부가 없으므로 중괄호 {} 대신 메서드 선언 뒤에 세미콜론(;)을 사용한다. 이 메소드는 상속받는 클래스에서 <strong>재정의(Override)</strong>하여 구체적인 구현을 제공해야 한다.</p>
<p>Phone의 예시를 다시 사용하자면 아래의 코드처럼 추상 메소드를 선언할 수 있다.</p>
<pre><code class="language-java">package abstractclass;

public abstract class Phone {

    // 필드
    public String user;

    // 생성자
    public Phone(String user) {
        this.user = user;
    }

    // 추상 메소드
    public abstract void makeCall(); 

}
</code></pre>
<p>이렇게 선언한 추상 메소드는 상속을 받는 실체 클래스에서 무조건 직접 재정의하여 내부 로직을 작성해야 한다. 아래의 코드를 참고하길 바란다.</p>
<pre><code class="language-java">package abstractclass;

public class SmartPhone extends Phone {
    // 생성자
    public SmartPhone(String user) {
        super(user);
    }

    // 추상 메소드 재정의
    @Override
    public void makeCall() {
        System.out.println(&quot;스마트 폰으로 전화 걸기&quot;);
    }
}
</code></pre>
<pre><code class="language-java">package abstractclass;

public class TelePhone extends Phone {

    public TelePhone(String user) {
        super(user);
    }

    // 메소드
    @Override
    public void makeCall() {
        System.out.println(&quot;텔레폰으로 전화 걸기&quot;);
    }

}</code></pre>
<br />

<blockquote>
<p><strong>참고</strong>
<a href="https://limkydev.tistory.com/188">https://limkydev.tistory.com/188</a>
혼공자 - 379p ~ 387p</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🌐 인터넷]]></title>
            <link>https://velog.io/@hk_98/%EC%9D%B8%ED%84%B0%EB%84%B7</link>
            <guid>https://velog.io/@hk_98/%EC%9D%B8%ED%84%B0%EB%84%B7</guid>
            <pubDate>Thu, 16 Jan 2025 07:10:10 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-알아두어야-할-용어">📌 알아두어야 할 용어</h2>
<hr>
<ul>
<li><strong>이더넷 케이블</strong> : 원래 전화선 용도로 쓰이다가 현대에서는 주로 근거리 통신망(Local Area Network)을 구축하기 위해 사용되는 케이블이다. 가장 흔하게는 랜선이라고 불린다.</li>
<li><strong>IP(Internet Protocol)</strong> : 인터넷에 연결되어 있는 모든 장치들(컴퓨터, 서버 장비, 스마트폰 등)을 식별할 수 있도록 각각의 장비에게 부여되는 고유 주소이다.</li>
<li><strong>클라이언트</strong> : 데이터나 서비스를 요청하는 역할을 하는 컴퓨터나 장치를 의미한다. 예를 들어, 우리가 웹 브라우저(Chrome, Edge 등)를 사용하여 웹사이트에 접속하거나, 스마트폰으로 이메일을 확인할 때, 이 브라우저나 스마트폰은 클라이언트 역할을 한다.</li>
<li><strong>서버</strong> : 클라이언트의 요청에 응답하여 데이터를 제공하는 역할을 하는 컴퓨터나 장치를 의미한다. 서버는 항상 네트워크에 연결되어 있어 클라이언트가 요청할 때 데이터를 처리하고 응답한다.</li>
</ul>
<br/>

<h2 id="🔍-인터넷이-뭐에요">🔍 인터넷이 뭐에요?</h2>
<hr>
<p><span style="color:orange"><strong>인터넷이란?</strong></span> 여러 통신망을 하나로 연결한다는 의미의 인터 네트워크(inter-network)라는 말에서 시작되었으며, 전 세계 컴퓨터들을 하나로 연결하는 거대한 통신망을 의미한다. 이러한 인터넷은 클라이언트와 서버로 구성되며, TCP/IP라는 기본 프로토콜을 통해 제공되고 있다. 즉, 인터넷이란 수많은 클라이언트 컴퓨터와 서버 컴퓨터, 그리고 이들로 구성된 네트워크들의 집합체인 것이다.</p>
<br/>

<h2 id="✨-인터넷의-특징">✨ 인터넷의 특징</h2>
<hr>
<ol>
<li>인터넷은 서로 동시에 참여할 수 있는 쌍방향 통신을 제공한다.</li>
<li>컴퓨터는 저장이 가능하므로 메시지를 보내는 사람과 받는 사람 모두 시간에 제약을 받지 않고 컴퓨터가 네트워크에 연결만 되어 있다면 언제든지 메시지를 주고받을 수 있다.</li>
<li>초기 인터넷에서는 텍스트로만 통신이 가능했지만 현재에는 이미지, 음성, 동영상 등 다양한 포맷으로 통신이 가능하다.</li>
<li>인터넷은 직업이나 사회적 지위, 직책, 인종, 나이 등을 서로 알 수 없는 익명성을 제공한다.</li>
</ol>
<br/>

<h2 id="📡-인터넷의-작동원리">📡 인터넷의 작동원리</h2>
<hr>
<h3 id="인터넷은-어떻게-연결이-되어있는가">인터넷은 어떻게 연결이 되어있는가?</h3>
<p>예를 들어, 두 개의 컴퓨터가 통신이 필요할 때, 우리는 다른 컴퓨터와 물리적으로 이더넷 케이블 또는 무선(WIFI, Bluetooth 등)으로 연결되어 있어야 한다. </p>
<p align='center'>
  <img src="https://velog.velcdn.com/images/hk_98/post/32d16dcb-eaae-44d9-99d7-1d79b066a099/image.png" width="50%" height="50%">

</p>


<p>하지만 현대에서의 네트워크는 두 대의 컴퓨터만 쓰는 것이 아닌 수많은 컴퓨터들이 연결되어 있다. 이러한 수많은 컴퓨터들을 이더넷 케이블 및 무선으로 일대일로 연결을 하게 되면 수많은 케이블이 필요하게 되어 비효율적이다.</p>
<p align='center'>
  <img src="https://velog.velcdn.com/images/hk_98/post/0c15c1f0-d13a-45f7-8f98-38704dc383fb/image.png" width="40%" height="40%">
</p>


<p>이 문제를 해결하기 위해 네트워크의 각 컴퓨터는 라우터라는 특수한 장치에 연결하는 방법을 사용하는데 이렇게 라우터를 사용하게 되면 아래의 그림처럼 10개의 케이블만 사용하게 되어 더 효율적이게 된다. 여기서 <strong>라우터</strong>란 둘 이상의 패킷 전환 네트워크 또는 서브 네트워크를 연결하는 장치이며 라우터는 데이터 패킷을 의도한 IP 주소로 전달하여 이러한 네트워크 간의 트래픽을 관리하고, 여러 장치가 동일한 인터넷 연결을 사용할 수 있도록 하는 등, 두 가지 주요 기능을 제공하는 역할을 한다. 라우터를 항공 교통관제사로, 네트워크로 전송되는 데이터 패킷을 다른 공항으로 향하는 비행기로 생각하면 비유가 쉬울 것이다.</p>
<p align='center'>
  <img src="https://velog.velcdn.com/images/hk_98/post/25a5e18b-3286-48b0-a06b-b25daa85e439/image.png" width="40%" height="40%">
</p>


<p>또한 컴퓨터와 컴퓨터끼리 연결이 가능하듯이 라우터와 라우터끼리도 연결이 가능하다. 이러한 방식으로 네트워크를 무한히 확장할 수 있는 것이다.</p>
<p align='center'>
  <img src="https://velog.velcdn.com/images/hk_98/post/7c2e3453-d82c-437e-8437-d3be89d7ca72/image.png" width="40%" height="40%">
</p>

<p>하지만 이 방식을 우리가 인터넷이라고 부르는 것에 매우 가깝지만, 한 가지 놓치고 있는 것이 있다. 우리가 오늘날의 인터넷으로 이웃과 근처에 사는 친구 혹은 가족들하고만 인터넷으로 소통을 하는 것이 아니고 전 세계 사람들과 소통을 하는 것인데 이렇게 멀리 살고 있는 사람들과도 인터넷으로 연결되기 위해서는 아주 먼 곳까지 유선 케이블을 연결을 할 수는 없으니 집집마다 있는 전화선을 통해 장거리 네트워크를 연결하기로 했다. 이러한 전화선을 이용해 네트워크와 전화 시설을 연결하기 위하여 <strong>모뎀</strong>이라는 특수한 장비를 사용한다. 이 장치는 우리 네트워크의 정보를 전화 시설에서 처리할 수 있는 정보로 바꾸며, 그 반대의 경우도 마찬가지이다.</p>
<p align='center'>
  <img src="https://velog.velcdn.com/images/hk_98/post/453c248f-0116-45f0-a56c-c4c6b9c2eb1b/image.png" width="50%" height="50%">
</p>

<p>다음으로 모뎀을 통해 전화 시설에 연결이 되면 장거리로 데이터를 주고받기 위해 네트워크를 <strong>인터넷 서비스 제공 업체 (Internet Service Provider, ISP)</strong> 에 연결한다. ISP는 모두 함께 연결되는 몇몇 특수한 라우터를 관리하고 다른 ISP의 라우터에도 액세스할 수 있는 회사이다. 우리나라에는 SKT, KT, LG 등이 있다. 이렇게 우리 네트워크의 메시지는 ISP 네트워크의 네트워크를 통해 대상 네트워크로 전달된다. 따라서 인터넷은 이러한 전체 네트워크 인프라로 구성된다.</p>
<p align='center'>
  <img src="https://velog.velcdn.com/images/hk_98/post/a680cdf0-706a-4414-9096-f463ceab2110/image.png" width="20%" height="30%">
</p>


<h3 id="그렇다면-인터넷을-통해-어떻게-데이터를-주고-받는가">그렇다면 인터넷을 통해 어떻게 데이터를 주고 받는가?</h3>
<p>인터넷에서 데이터를 주고받는 과정은 DNS 요청부터 데이터 패킷 전송까지 여러 단계를 거칩니다. 각 단계에서 어떤 일이 일어나는지 살펴보겠습니다. 만약 우리가 구글에 접속을 한다고 가정을 해보자 URL로 <code>www.google.com</code>을 입력하게 되면 다음과 과정을 거쳐서 데이터를 주고받게 된다.</p>
<h4 id="1️⃣-도메인-이름을-ip로-변환-dns-요청">1️⃣ 도메인 이름을 IP로 변환 (DNS 요청)</h4>
<p>이 단계를 설명하기 전에 DNS에 대해서 알 필요가 있는데 간단하게 설명하자면 <strong>DNS(Domain Name System)</strong> 란 우리가 웹사이트에 접속을 하려고 할 때 그 서버의 IP 주소를 알아야 하는데 예를 들어, 192.xxxx.xxxx로 이루어진 IP 주소를 입력하여 접속을 하려고 하면 이를 기억하고 있어야 하는데 이러한 IP 주소를 사람들이 외우기 쉽지 않기 때문에 이를 사람이 읽고 기억하기 쉬운 이름으로 특정 웹사이트에 접속할 수 있도록 해주는 것이다.</p>
<p>즉, 여기서 우리가 입력한 <code>www.google.com</code>은 사람이 읽기 쉽도록 만든 도메인 주소이다. 이렇게 구글 도메인 주소를 입력을 하여 접속을 시도하면 브라우저는 DNS 서버를 통해 입력한 URL의 IP 주소를 찾는다. </p>
<h4 id="2️⃣-데이터-패킷-생성">2️⃣ 데이터 패킷 생성</h4>
<p>우리가 입력한 도메인의 IP 주소를 확인한 뒤, 브라우저는 해당 서버에 데이터를 요청하기 위해 <strong>요청 메시지(Request Message)</strong> 를 만든다. 이 요청 메시지는 인터넷을 통해 전달될 수 있는 작은 조각들로 나뉘며, 이를 <strong>데이터 패킷(Packet)</strong> 이라고 한다. 한마디로, 인터넷에서는 용량이 큰 요청을 한 번에 보내는 대신, 작은 조각들로 나누어 전송한다고 보면 된다. </p>
<p>이렇게 하면 네트워크의 효율성을 높이고, 전송 중 일부 데이터가 손실되더라도 전체 데이터의 복구가 가능하다. 데이터 패킷은 <strong>헤더</strong>와 <strong>페이로드</strong> 그리고 <strong>트레일러</strong> 세 부분으로 구성된다. 헤더에는 패킷 길이, 프로토콜, 보낸/받는 사람의 IP 주소 등이 포함된다. 페이로드는 패킷의 본문에 해당하며 실제 테이터를 담고 있는 부분이다. </p>
<p>쉽게 말해, 페이로드는 인터넷에서 실질적으로 주고받고 싶은 데이터를 담고 있는 부분이다. 트레일러는 데이터 전송이 완료된 후 패킷의 끝부분에 추가되는 정보로, 주로 오류검사에 사용된다.</p>
<h4 id="3️⃣-데이터-전송-라우터와-네트워크-공유">3️⃣ 데이터 전송 (라우터와 네트워크 공유)</h4>
<p>생성된 패킷은 사용자의 집을 떠나서 ISP로 도달하게 되고 이러한 ISP 네트워크에서 라우터가 패킷을 목적지로 보내기 위해 최적의 경로를 선택하고 전송한다. 전송하게 되면 패킷은 여러 네트워크를 거치며 구글의 서버가 있는 데이터 센터로 이동한다. 먼 거리로 전송을 할 때에는 경우에 따라 해저 광케이블이나 위성을 통해 대륙 간 이동이 이루어지는데 오늘날 대부분 해저 광케이블을 통해서 주로 대륙 간 데이터 전송이 이루어진다.</p>
<h4 id="4️⃣-구글-서버에서-데이터-처리-및-응답-생성">4️⃣ 구글 서버에서 데이터 처리 및 응답 생성</h4>
<p>패킷이 구글 서버에 도착하면, 서버는 이를 확인하고 처리한다. 패킷의 헤더와 페이로드, 트레일러를 분석하여 어떤 데이터를 요청했는지 파악한다. 파악한 후 요청에 맞는 응답 데이터를 필요한 경우 데이터베이스 조회나 내부 연산을 통해 응답 데이터를 생성한다. 요청과 마찬가지로 데이터를 데이터 패킷으로 나누어서 생성한다.</p>
<h4 id="5️⃣-응답-데이터-전송">5️⃣ 응답 데이터 전송</h4>
<p>구글 서버가 준비한 응답 데이터 패킷은 네트워크를 통해 요청과 같은 방법으로 다시 사용자에게 전송된다. 이 과정에서도 패킷은 여러 네트워크를 거친다.</p>
<h4 id="6️⃣-사용자의-브라우저에서-데이터-조립-및-렌더링">6️⃣ 사용자의 브라우저에서 데이터 조립 및 렌더링</h4>
<p>사용자에게 전송된 응답 데이터를 바탕으로 브라우저는 응답받은 데이터 패킷을 재구성해 원래의 데이터를 복원한다. 복원한 다음 데이터를 화면에 렌더링(rendering) 하여 사용자게 구글 메인 페이지를 볼 수 있도록 한다.</p>
<h4 id="📊-요약">📊 요약</h4>
<ol>
<li>사용자가 <a href="http://www.google.com">www.google.com</a> 입력 → 브라우저가 DNS 요청으로 IP 주소를 얻음.</li>
<li>데이터 요청 메시지를 생성해 패킷으로 나눔.</li>
<li>패킷이 네트워크를 통해 서버로 전송.</li>
<li>서버에서 요청 처리 후 응답 데이터 생성.</li>
<li>응답 데이터가 네트워크를 통해 다시 사용자에게 전송.</li>
<li>브라우저가 데이터를 재구성해 화면에 표시.</li>
</ol>
<br />

<blockquote>
<p><strong>참고</strong>
인터넷 개념 - <a href="https://www.tcpschool.com/webbasic/intro">https://www.tcpschool.com/webbasic/intro</a>
인터넷 작동원리(1) -<a href="https://developer.mozilla.org/ko/docs/Learn_web_development/Howto/Web_mechanics/How_does_the_Internet_work">https://developer.mozilla.org/ko/docs/Learn_web_development/Howto/Web_mechanics/How_does_the_Internet_work</a>
인터넷 작동원리(2) - <a href="https://gyunny.tistory.com/2">https://gyunny.tistory.com/2</a>
패킷 - <a href="https://jdcyber.tistory.com/12">https://jdcyber.tistory.com/12</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[싸피 2학기 자율 프로젝트 (Port Alio)]]></title>
            <link>https://velog.io/@hk_98/%EC%8B%B8%ED%94%BC-2%ED%95%99%EA%B8%B0-%EC%9E%90%EC%9C%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Port-Alio</link>
            <guid>https://velog.io/@hk_98/%EC%8B%B8%ED%94%BC-2%ED%95%99%EA%B8%B0-%EC%9E%90%EC%9C%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Port-Alio</guid>
            <pubDate>Mon, 06 Jan 2025 06:11:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>프롤로그 - 자율 프로젝트만을 남겨두면서</p>
</blockquote>
<p>2학기에서 진행하는 총 3개의 프로젝트 중 2개가 마무리 되었고 어느덧 자율 프로젝트에 들어서게 되었다. 자율 프로젝트는 말 그대로 팀에서 원하는 주제 및 도메인을 선택해서 진행하는 프로젝트이며 마지막 프로젝트라서 그런지 싸피에서 지원을 엄청해주셨다. 예를 들어, 학습자료 및 구독 서비스 등을 지원해준다. 이러한 지원을 받는 프로젝트이고 싸피에서의 마지막 프로젝트이며 또한 IT 프로젝트에서 처음 팀장을 맡게 되어서 그런지 잘할 수 있을지에 대한 불안감이 있었다.</p>
<br/>

<blockquote>
<p>본론 - PortAlio</p>
</blockquote>
<h2 id="🗂️-portalio">🗂️ PortAlio</h2>
<hr>
<h3 id="🌐-프로젝트-주제">🌐 프로젝트 주제</h3>
<p>모든 직무의 사람들을 위한 포트폴리오 공유 및 포트폴리오 기반 AI 모의 면접 플랫폼 </p>
<h3 id="📅-개발-기간">📅 개발 기간</h3>
<p><code>10월 14일 ~ 11월 19일</code></p>
<h3 id="📋-기획-배경">📋 기획 배경</h3>
<p>개발자들은 깃허브를 디자이너들은 Behance 등을 통해 포트폴리오를 관리하는 플랫폼이 존재하는데 다른 직무의 사람들은 따로 관리하는 플랫폼이 부재하여 개인 컴퓨터에 저장하거나 블로그에 활동 경험 글을 게시하는 것 등이 다여서 이러한 점을 주목하여 기획하게 되었습니다.</p>
<h3 id="🎯-목표">🎯 목표</h3>
<p>모든 직무를 희망하거나 종사하는 사람들의 포트폴리오를 체계적으로 관리 및 공유 할 수 있도록 하여 본인의 커리어를 알리거나 취업 및 이직에 도움을 줄 수 있도록 하는 것이 목표입니다.</p>
<h3 id="📊-erd">📊 ERD</h3>
<p><img src="https://velog.velcdn.com/images/hk_98/post/1e5017b9-2757-4399-9d15-19c5ac8a845d/image.png" alt=""></p>
<h3 id="🛠️-사용-기술">🛠️ 사용 기술</h3>
<ul>
<li>클라이언트 : <code>React</code>, <code>TypeScript</code>, <code>Tailwind.css</code>, <code>redux-toolkit</code></li>
<li>서버 : <code>Springboot</code>, <code>Spring Security</code>, <code>oAuth2</code>, <code>JWT</code> <code>FastAPI</code></li>
<li>데이터베이스 : <code>MYSQL</code>, <code>MongoDB</code></li>
<li>인프라 : <code>AmazoneEC2</code>, <code>Docker</code>, <code>Jenkins</code>, <code>Nginx</code></li>
<li>협업툴 : <code>JIRA</code></li>
<li>형상관리 : <code>Gitlab</code></li>
</ul>
<h3 id="🏗️-시스템-아키텍처">🏗️ 시스템 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/hk_98/post/7c4170f0-45c9-4009-b04d-78b89abbb31a/image.png" alt=""></p>
<h3 id="👨👩👧👦-팀원-정보-및-업무-담당-내역">👨‍👩‍👧‍👦 팀원 정보 및 업무 담당 내역</h3>
<hr>
<h4 id="팀구성">팀구성</h4>
<p>김헌규(팀장 - 풀스택), 김은섭(팀원 - AI), 박재영(팀원 - 풀스택, AI), 윤민재(팀원 - 풀스택, AI), 이진송(팀원 - 풀스택), 차상곤(팀원 - 풀스택, 인프라)</p>
<h4 id="내가-담당한-업무-내역">내가 담당한 업무 내역</h4>
<p>백엔드</p>
<ul>
<li><strong>Spring security</strong>와 <strong>JWT</strong>와 <strong>oAuth2</strong>를 이용한 회원 시스템 구축</li>
<li>회원 시스템 구축에 따른 회원 관련 API 설계 및 작성</li>
<li>프론트 담당한 부분을 수행하면서 관련 API 수정 및 보충</li>
</ul>
<p>프론트 엔드</p>
<ul>
<li>회원가입 및 로그인 페이지</li>
<li>AI 모의면접 진행 페이지</li>
<li>AI 모의면접 결과 분석 페이지</li>
<li>게시판 전체 글 조회, 검색, 상세조회 페이지</li>
<li>게시판 글 조회 시 무한 스크롤 구현</li>
</ul>
<h3 id="✨-주요-기능">✨ 주요 기능</h3>
<hr>
<h4 id="markdown-방식의-에디터를-통한-글-작성-기능">MarkDown 방식의 에디터를 통한 글 작성 기능</h4>
<p>Markdown으로 손쉽게 글 작성이 가능하며 추가적으로 포트폴리오, 경력 기술서 등의 기본 양식을 제공하여 글을 더 쉽게 작성할 수 있도록 하였습니다.</p>
<p>작성할 수 있는 게시글은 포트폴리오, 레포지토리, 활동, 자유, 질문 게시글 등이 있습니다. </p>
<img src="https://github.com/nosong2/portalio/blob/main/exec/gifs/%EA%B2%8C%EC%8B%9C%EA%B8%80%20-%20%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4.gif?raw=true">


<h4 id="ai모의-면접-및-면접-결과-분석-기능">AI모의 면접 및 면접 결과 분석 기능</h4>
<p>사용자가 작성한 포트폴리오를 기반으로 화상, 음성, 텍스트 방식으로 AI 모의 면접을 할 수 있습니다.</p>
<p>또한 tts &amp; stt 를 활용하여 사용자의 포트폴리오 기반 사용자 맞춤 면접 질문 생성하였습니다. </p>
<p>화상 면접의 경우 화상 데이터 분석(AI Hub - Resnet-50) 면접 영상 기반 안면 인식(랜드마크) 활용 사용자 감정 분석(면접 집중도 분석) 동공 랜드마크, 상체 움직임 등 자료 활용하여 면접 집중도를 분석 하였습니다.</p>
<p>음성 면접의 경우 음성 데이터 분석(AI Hub - Resnet-50) 음성의 높낮이, 떨림 등 자료 활용하여 면접 집중도를 분석 하였습니다.</p>
<p>GPT API를 활용하여 stt 과정에서 나온 데이터를 읽기 편하게 다듬어서 사용자에게 노출시켰습니다.</p>
<img src="https://github.com/nosong2/portalio/blob/main/exec/gifs/%EB%AA%A8%EC%9D%98%EB%A9%B4%EC%A0%91%20%EB%A1%9C%EB%B9%84%EA%B9%8C%EC%A7%80.gif?raw=true">


<img src="https://github.com/nosong2/portalio/blob/main/exec/gifs/%EB%AA%A8%EC%9D%98%ED%99%94%EC%83%81%EB%A9%B4%EC%A0%91.gif?raw=true">

<h4 id="채용-정보-제공-기능">채용 정보 제공 기능</h4>
<p>원하는 직무를 선택하면 직무와 관련된 채용 정보를 알려주는 기능입니다.</p>
<img src="https://github.com/nosong2/portalio/blob/main/exec/gifs/%EC%B1%84%EC%9A%A9%EC%A0%95%EB%B3%B4.gif?raw=true">


<h3 id="🚨-트러블-슈팅">🚨 트러블 슈팅</h3>
<h4 id="문제-1">문제 1</h4>
<ul>
<li><strong>문제</strong> : spring을 실행한 후 특정 로직을 수행할 때com.fasterxml.jackson.databind.exc.invaliddefinitionexception: no serializer found for class org.hibernate.proxy.pojo.bytebuddy.bytebuddyinterceptor and no properties discovered to create beanserializer 에러가 발생. 아래의 코드처럼 entity에서 다루는 데이터를 그대로 반환할 경우 발생하였습니다.</li>
<li><strong>문제 원인</strong> : Hibernate는 엔티티를 지연 로딩(lazy loading)할 때 프록시 객체를 사용하여 데이터베이스로부터 실제 데이터를 가져오지 않은 상태로 유지하는데 이 프록시 객체는 Jackson과 같은 JSON 직렬화 라이브러리가 이를 직렬화하려고 할 때 문제가 발생하게 되며 Jackson은 이 프록시 객체의 데이터를 직렬화할 수 없기 때문에, no serializer found 에러가 발생했습니다.</li>
<li><strong>해결 방법</strong> : 직렬화 방법으로 DTO를 사용하였고 from, to, of와 같은 정적 메서드를 활용하여 DTO로 변환하는 방식으로 해결하였습니다.</li>
</ul>
<h4 id="문제-2">문제 2</h4>
<ul>
<li><strong>문제</strong> : 화상 면접 수행 과정 중 답변을 화상과 음성을 따로 녹화 녹음하여 fastAPI 서버로 보내고 있었는데 S3에 저장한 화상 및 음성 파일이 첫번째 질문에 대한 답변들을 잘 저장이 되는데 두번째 세번째 갈수록 이전 영상 및 음성의 데이터가 남아있고 해당 질문에 대한 답변이 잘 녹화 및 녹음이 되지 않는 문제가 발생하였습니다.</li>
<li><strong>문제 원인</strong> : 이전 질문에 대한 답변을 녹화 및 녹음 해두는 videoChunksRef와 audioChunkRef를 변수를 비워주지 않았기 때문에 이전 기록에 대한 내용들이 담겨 있었기 때문에 발생했습니다.</li>
<li><strong>해결 방법</strong> : 녹화와 녹음에 대한 데이터를 담는 변수인 videoChunksRef와 audioChunkRef를 녹화 및 녹음이 끝날 때마다 초기화를 시켜주어 해결을 하였습니다.</li>
</ul>
<h3 id="🤔-프로젝트-회고">🤔 프로젝트 회고</h3>
<ul>
<li><p><strong>아쉬웠던 점</strong></p>
<ul>
<li>이번에 IT 프로젝트에서 <strong>팀장</strong>을 처음 맡아서 프로젝트를 진행했는데 바쁜 시기에 너무 정신이 없었어서 디테일한 부분을 신경써주긴 했으나 완벽하게 신경을 잘 써주지 못했다는 점.</li>
<li>마감 마지막 날까지 기능 구현을 했었는데 일정 조율 면에서도 팀원들의 수준을 잘 신경써서 진행하지 못했던 점.</li>
<li>프론트엔드 및 백엔드 테스트 코드를 짤 시간이 없어서 작성하지 못했던 점.</li>
</ul>
</li>
<li><p><strong>개선 방안</strong></p>
<ul>
<li>기존 팀 스크럼은 매일 진행하였으나 <strong>개인간의 1:1 미팅</strong>을 하지 않았어서 정기적인 1:1 미팅을 통해 팀원들의 어려움이나 의견을 청취하고, 필요한 지원을 제공하는 방법으로 개선하기.</li>
<li>바쁜 와중에도 정신 없어 하지말고 좀 더 넓게 진행 상황 및 요청 사항을 바라보기 등을 개선 하기.</li>
</ul>
</li>
<li><p><strong>깨달은 점</strong></p>
<ul>
<li><strong>문제 해결에서의 교훈</strong><ul>
<li>이번 프로젝트에서는 <strong>기술적 문제를 해결하며, 문제의 원인을 명확히 분석</strong>하는 것이 얼마나 중요한지 깨달았습니다. 특히 Hibernate에서 발생한 <code>no serializer found</code> 오류를 해결하며, <strong>DTO를 적극적으로 활용하는 방법</strong>을 익혔고, 이를 통해, <strong>데이터 처리와 API 설계</strong>에 대한 더 깊은 이해를 얻을 수 있었습니다.</li>
</ul>
</li>
<li><strong>팀장으로서의 성장</strong><ul>
<li>처음으로 팀장을 맡아 프로젝트를 진행하면서 <strong>팀원 간의 원활한 소통</strong>과 <strong>일정 조율의 중요성</strong>을 절실히 느꼈습니다. 팀원 각각의 <strong>역량과 상태를 이해하지 못한 채 일정과 작업을 배분</strong>한 것이 프로젝트 후반으로 갈수록 비효율적으로 작용했음을 깨달았습니다. 이 경험을 통해 정기적인 1:1 미팅과 세부적인 작업 분담이 필요하다는 점을 배웠으며, 앞으로는 <strong>더 나은 리더십</strong>을 발휘할 수 있도록 노력하겠습니다.</li>
</ul>
</li>
<li><strong>기술적 성장</strong><ul>
<li>JWT와 OAuth2를 사용한 <strong>회원 인증 시스템</strong>을 구축하며 <strong>보안의 중요성</strong>을 실감했고, 특히 토큰 기반 인증에서 <strong>refresh token과 access token</strong>을 효과적으로 사용하는 방법을 배웠습니다. 또한 Spring Security와 <strong>필터 체인을 설계하며, 인증 과정의 구조적 이해</strong>를 깊이 있게 다질 수 있었습니다.</li>
</ul>
</li>
<li><strong>향후 다짐</strong><ul>
<li>이번 경험을 통해 <strong>문제 해결 능력</strong>과 <strong>리더십 역량</strong>의 부족한 부분을 알게 되었고, 이를 보완하기 위해 프로젝트 관리 도구(<strong>Jira, Gantt Chart</strong> 등)의 활용도를 높이고, <strong>정기적으로 팀원들과 소통</strong>하며 프로젝트를 성공적으로 이끌어 나갈 것입니다. 또한, 기술적인 깊이를 더해 더 나은 해결책을 제시할 수 있는 개발자가 되도록 성장해 나가겠습니다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br/>

<blockquote>
<p>결론</p>
</blockquote>
<h3 id="😊-후기">😊 후기</h3>
<p>자율 프로젝트를 마지막으로 싸피에서의 모든 프로젝트를 마무리하게 되었는데 이번 프로젝트는 비전공자 6명이서 한 프로젝트여서 퀄리티적으로 많이 미숙하였지만 나를 포함한 팀원들 모두 실력적으로 폭풍 성장한 계기가 되어 제일 보람찬 프로젝트였지 않나 싶다.</p>
<p>또한 이번에 개발 프로젝트에서 첫 팀장을 맡아서 진행하였는데 처음 기획 단계부터 열정만 앞섰기에 너무 복잡하게 기획을 하여서 팀원들이 정신 없었지 싶은데 기획 이후 개발과 배포까지 잘 따라와준 팀원들에게 정말 고맙다는 말을 전하고 싶다.(모두 고생했어요!! ㅎㅎ) </p>
<p>돌이켜보면 마지막까지 시간에 대한 압박을 받으면서 개발을 하다 보니 스스로 나 자신에 대해서 질책하고 또 팀원들에게 압박을 넣고 불평불만을 늘어놓았던 적이 있는데 이러한 모습이 지금 봐서는 너무 부끄럽다. 그래서 앞으로 하는 팀 프로젝트에 있어서는 좀 더 단단한 사람이 되어서 어떠한 시련이 와도 아무런 불평 없이 묵묵히 해내는 사람으로 성장하도록 노력을 해야겠다는 생각이 들었다.</p>
<p>마무리로 1년이라는 시간 동안 싸피에서 파이썬으로 알고리즘을 풀고 4개의 프로젝트를 진행했는데 정말 힘들었는지 1년이라는 시간이 흘렀는지도 모를만큼 빠르게 흘렀다. 그만큼 인생에 있어서 제일 몰입했던 시간이라고 정의할 수 있을듯하다. 앞으로는 취업 준비를 할 예정인데 나를 포함한 모든 싸피 분들이 빠른 취업을 할 수 있기를 바란다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[싸피 2학기 특화 프로젝트(Dropzone)]]></title>
            <link>https://velog.io/@hk_98/%EC%8B%B8%ED%94%BC-2%ED%95%99%EA%B8%B0-%ED%8A%B9%ED%99%94-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8Dropzone</link>
            <guid>https://velog.io/@hk_98/%EC%8B%B8%ED%94%BC-2%ED%95%99%EA%B8%B0-%ED%8A%B9%ED%99%94-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8Dropzone</guid>
            <pubDate>Sat, 07 Dec 2024 11:19:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>프롤로그 - 특화 프로젝트를 시작하며</p>
</blockquote>
<p>최근까지 하던 공통 프로젝트를 마무리하고 드디어 2학기의 특화 프로젝트를 시작하게 되었다. 공통 프로젝트가 끝나갈 무렵 특화 프로젝트를 위해 팀원들을 구하는 주변 싸피생들이 보였고 그때 나도 특화 팀에 대해 고민하게 되었다. 사실 이번 프로젝트에서는 백엔드 개발자를 지망하는 사람으로써 프론트엔드는 가급적이면 하고 싶지 않았다. 그래서 많은 팀들을 고심해서 둘러 보았는데 보통 싸피에서는 전공자들이 주로 백엔드를 맡는데 솔직히 말해서 아직 Java도 제대로 모르고 spring도 모르는 나의 입장에서는 백엔드를 희망하는 전공자 사이에서 백엔드를 하고 싶다는 의견을 쉽사리 내지 못할뿐더러 의견을 낸다고 해도 팀을 위해 기각당할 확률이 높았기 때문에 최대한 백엔드를 맡을 확률이 높은 팀을 찾아다녔고 그 중 메타버스 게임 팀에서 백엔드 개발자를 필요로 했고 게임 프로젝트 특성상 spring 백엔드가 구현할 기능들이 많지는 않았기 때문에 메타버스 게임 팀에 들어가는 것을 결정하게 되었다.</p>
<blockquote>
<p>본론 - 싸피 2학기 특화 프로젝트 (Dropzone)</p>
</blockquote>
<h2 id="👾dropzone">👾Dropzone</h2>
<hr>
<h3 id="🕹️-프로젝트-주제">🕹️ 프로젝트 주제</h3>
<p>싸피생들끼리 친목을 위해 제작한 6인 배틀로얄 게임.</p>
<h3 id="📅-개발-기간">📅 개발 기간</h3>
<p><code>8월 26일 ~ 10월 11일</code></p>
<h3 id="📋-기획-배경">📋 기획 배경</h3>
<p>싸피 2학기에는 총 3번의 프로젝트를 진행하는데 친한 사람들하고 팀을 짜는 경우도 있지만 그렇지 않은 경우가 있는데 그럴때 어색한 팀원들과 친해지기 위해 다양한 컨텐츠를 생각하는 팀장들이 있다는 것을 알게 됨.</p>
<h3 id="🎯-목적">🎯 목적</h3>
<p>싸피에서 어색한 팀원들 또는 일상 생활에서 어색한 사람들과 친목도모를 위해 게임을 제작하여 제공하면 어떨까해서 기획을 하게 되었고 다양한 게임 장르들을 생각하다가 팀원들과 상의한 결과 6인 배틀로얄 게임을 기획하게 되었으며 즉, 목적으로는 싸피의 팀장들이 팀원들과 좀 더 친목도모를 수월하게 할 수 있도록 게임 컨텐츠를 제공하는 것이다.</p>
<h3 id="📊-erd">📊 ERD</h3>
<p><img src="https://velog.velcdn.com/images/hk_98/post/3a6b0098-d6f7-478c-bd52-891c9456ee2c/image.png" alt=""></p>
<h3 id="🛠️-사용-기술">🛠️ 사용 기술</h3>
<ul>
<li>클라이언트 : <code>Unity</code>, <code>Photon pun2</code></li>
<li>서버 : <code>Spring boot</code>, <code>Spring security</code></li>
<li>데이터베이스 : <code>mySQL</code>, <code>mongoDB</code></li>
<li>인프라 : <code>AmazoneEC2</code>, <code>Docker</code>, <code>Jenkins</code>, <code>NGINX</code></li>
<li>협업툴 : <code>JIRA</code></li>
<li>형상관리 : <code>Gitlab</code>, <code>PlasticSCM</code></li>
</ul>
<h3 id="🏗️-시스템-아키텍처">🏗️ 시스템 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/hk_98/post/0a0a2619-2877-4775-aa59-23561b1acc5c/image.png" alt=""></p>
<h3 id="👨👩👧👦-팀원-정보-및-업무-담당-내역">👨‍👩‍👧‍👦 팀원 정보 및 업무 담당 내역</h3>
<hr>
<h4 id="팀-구성">팀 구성</h4>
<p>황준(팀장 - 클라이언트), 김동준(팀원 - 클라이언트), 손동희(팀원 - 클라이언트), 박재영(팀원 - 클라이언트, 서버), 차상곤(팀원 - 서버), 김헌규(팀원 - 서버)</p>
<h4 id="내가-담당한-업무-내역">내가 담당한 업무 내역</h4>
<ul>
<li>Spring boot 서버 CI/CD 파이프 라인 구축</li>
<li>Spring STOMP를 사용한 로비 채팅 구현</li>
<li>친구 추가 기능 구현</li>
</ul>
<h3 id="✨-주요-컨텐츠">✨ 주요 컨텐츠</h3>
<hr>
<h4 id="최후의-1인">최후의 1인</h4>
<p>6명의 플레이어가 참여하는 배틀 로얄 게임으로, 최후의 1인이 남을 때까지 싸우고 생존하는 것이 목표이다.</p>
<h4 id="떨어지는-섬">떨어지는 섬</h4>
<p>일정 시간마다 맵이 떨어지면서 맵이 랜덤한 한 점으로 점점 줄어들게 된다. 각 플레이어는 점점 좁아지는 맵에서 생존하기 위해 떨어지는 구역을 피하며 싸워야 한다.</p>
<h4 id="4명의-캐릭터">4명의 캐릭터</h4>
<p>게임에는 4명의 캐릭터가 등장한다. 근접 캐릭터 2명, 원거리 캐릭터 2명으로 구성하였는데 근접 캐릭터로는 복서와 펑크 캐릭터가 있으며 원거리 캐릭터로는 솔저, 아처가 있다. 복서 캐릭터는 이름 그대로 복서의 외형을 갖춘 캐릭터이며 체력 110, 공격력 5, 스피드 7, 스킬 쿨타임 7의 스펙을 갖추고 있다. 스킬로는 슈퍼아머를 가지고 있으며 모든 상태이상을 해제할 수 있는 스킬을 가지고 있다. 펑크 캐릭터는 방망이를 든 불량배의 외형을 갖추고 있으며 체력 120, 공격력 15, 스피드 6, 스킬쿨타임 5의 스펙을 갖추고 있다. 스킬로는 방망이를 크게 휘둘러 적을 밀치며 밀쳐지는 적이 벽에 부딪히면 기절 상태에 빠진다. 솔저 캐릭터는 군인의 외형을 갖춘 캐릭터이며 체력 100, 공격력 10, 이동속도 5, 스킬 쿨타임 5의 스펙을 갖추고 있다. 스킬로는 플레이어와 오브젝트들을 관통하는 관통탄을 쏜다. 아처 캐릭터는 마치 툼레이더의 캐릭터와 닮은 외형을 갖추고 있으며 체력 100, 공격력 15, 스피드 5, 스킬 쿨타임은 6의 스펙을 갖추고 있다. 스킬로는 차징샷을 쏜다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/c1189676-311e-43df-bedd-b8b188c7b707/image.png" alt=""></p>
<h4 id="미니-게임">미니 게임</h4>
<p>본게임에 들어가기 전, 5단계로 구성된 미니 게임을 플레이하게 된다. 각 스테이지에는 총 4개의 포탈이 배치되어 있으며, 다른 유저들보다 빠르게 포탈을 이용해 다음 스테이지로 이동해야 한다. 가장 먼저 5스테이지에 도달한 플레이어는 본게임에서 사용할 수 있는 아이템을 보상으로 받는다.</p>
<h3 id="🎮-게임-이용-화면">🎮 게임 이용 화면</h3>
<hr>
<h4 id="회원-가입">회원 가입</h4>
<p><img src="https://velog.velcdn.com/images/hk_98/post/293a52ef-1033-465c-b261-29ade00058cf/image.gif" alt=""></p>
<h4 id="로그인">로그인</h4>
<p><img src="https://velog.velcdn.com/images/hk_98/post/99dfa14c-b373-441d-bff1-28d1bad5f3a2/image.gif" alt=""></p>
<h4 id="캐릭터-선택">캐릭터 선택</h4>
<p><img src="https://velog.velcdn.com/images/hk_98/post/81bbed0d-f2dd-4d84-b640-8b529263f226/image.gif" alt=""></p>
<h4 id="게임-매칭">게임 매칭</h4>
<p><img src="https://velog.velcdn.com/images/hk_98/post/02402d12-1d76-4746-af0c-ea375d263004/image.gif" alt=""></p>
<h4 id="아이템-파밍">아이템 파밍</h4>
<p><img src="https://velog.velcdn.com/images/hk_98/post/fe75138b-fe75-440b-a2c4-3816ff486e2a/image.gif" alt=""></p>
<h4 id="무기-업그레이드">무기 업그레이드</h4>
<p><img src="https://velog.velcdn.com/images/hk_98/post/67d28355-f06f-4a79-9d3d-60fc991950db/image.gif" alt=""></p>
<h4 id="배틀">배틀</h4>
<p><img src="https://velog.velcdn.com/images/hk_98/post/13e16e49-1156-4b90-ad57-c4c78a3e92c2/image.gif" alt=""></p>
<h3 id="😫-트러블-슈팅">😫 트러블 슈팅</h3>
<h4 id="문제-1">문제 1</h4>
<ul>
<li><strong>문제</strong> : Jenkins Script를 작성할 때 블로그를 참고하여 작성을 하였는데 Git Clone 단계에서 특정 파일을 찾을 수 없다는 에러가 계속 발생하였다.</li>
<li><strong>원인</strong> : 블로그를 참고할 때 폴더 구조를 우리 프로젝트의 폴더 구조를 기준으로 spring boot를 clone 했어야 했는데 이것을 신경쓰지 않아서 발생한 문제였다.</li>
<li><strong>해결 방법</strong> : 폴더 구조에 맞게 Script를 작성하였으며 Clone 이후 단계에서도 디테일하게 신경을 써줘서 무사히 CI/CD 파이프 라인을 구축할 수 있었다. 이러한 과정을 통해 Jenkins Script 작성법에 대해서 감을 잡게 되었다.</li>
</ul>
<h4 id="문제-2">문제 2</h4>
<ul>
<li><strong>문제</strong> : 친구 기능을 구현할 때 어떤 방식으로 구현할지 고민이었다.</li>
<li><strong>원인</strong> : Spring을 처음 접하면서 친구 기능을 구현하게 되었는데 처음 구현하다 보니 살짝 고민이 있었다.</li>
<li><strong>해결 방법</strong> : Friend Ship 테이블을 두고 테이블에 친구 관계가 될 2명의 user 행을 두었고 추가로 is_from이라는 행을 두어서 누가 보냈는지에 대한 여부를 두어 친구 요청을 보내고 받는 기능을 구현하였으며 거절을 할 경우 그 정보를 삭제하는 방식으로 친구 기능을 구현하였다.</li>
</ul>
<h4 id="문제-3">문제 3</h4>
<ul>
<li><strong>문제</strong> : Unity를 담당한 팀원과 STOMP를 사용한 로비 채팅 기능을 Unity와 채팅 연결 작업을 하면서 채팅 연결이 안되는 현상이 발생하였다.</li>
<li><strong>원인</strong> : Unity를 담당한 팀원이 연결을 하면서 제가 작성한 STOMP의 기능을 완벽히 이해하지 못했고 또한 Security와 JWT를 활용한 인증 인가 방식으로 회원 시스템을 구축하였기 때문에 토큰 처리를 하지 않았어서 발생했다.</li>
<li><strong>해결 방법</strong> : Unity를 담당한 팀원에게 STOMP의 작동 원리를 쉽게 설명을 해주었고 또한 토큰 처리에 대한 것도 알려주었습니다. 이렇게 팀원에게 관련 지식을 공유함으로써 문제를 빠르게 해결하였습니다.</li>
</ul>
<h3 id="🌟-프로젝트-진행-중-잘한-점">🌟 프로젝트 진행 중 잘한 점</h3>
<ul>
<li><strong>CI/CD 구축의 성공적인 완료</strong> : Docker와 Jenkins, Nginx를 활용하여 Spring Boot 서버의 CI/CD 파이프라인을 성공적으로 구축한 것은 프로젝트의 안정성과 생산성을 크게 향상시켰다.</li>
<li><strong>협업을 통한 성과 달성</strong> : Unity 팀원과의 협업을 통해 서로의 코드를 공유하여 효율적인 문제 해결을 하여 실시간 로비 채팅 기능을 성공적으로 구현하였고, 이렇게 구현한 채팅 기능으로 인해 실시간 통신이 게임 사용자 경험에 미치는 영향을 체감하였으며 이를 통해 협업의 중요성을 깨달았다.</li>
<li><strong>새로운 기술 습득 후 프로젝트에 적용</strong> : Spring Boot, JPA, STOMP, Docker, Jenkins 등 다양한 기술을 처음 사용하면서도 프로젝트의 요구사항에 맞게 실질적으로 적용하고 성공적인 결과를 만들어냈다.</li>
</ul>
<h3 id="🤔-프로젝트-진행-중-못한-점">🤔 프로젝트 진행 중 못한 점</h3>
<ul>
<li><strong>nginx를 굳이 사용했어야 하는 점</strong> : 사실 nginx를 학습의 목적으로 사용한 점이 컸다. Spring boot만 ec2에 띄워 놓기 때문에 https를 설정했다는 것 이외에는 nginx를 사용해야 할 이유가 없었는 점 때문이다.(하지만 학습용으로는 완전 좋았음...) </li>
</ul>
<h3 id="📚-프로젝트-진행-중-배운-점">📚 프로젝트 진행 중 배운 점</h3>
<ul>
<li>Spring의 기본 구조와 개념</li>
<li>Spring boot와 JPA를 사용한 CRUD</li>
<li>Spring STOMP를 사용한 간단한 채팅 구축</li>
<li>Docker의 개념 및 구조</li>
<li>Docker로 EC2에 컨테이너 구축</li>
<li>Jenkins를 사용한 CI/CD 스크립트 작성법</li>
<li>팀원과의 협업 및 소통 능력 향상</li>
</ul>
<blockquote>
<p>결론</p>
</blockquote>
<h3 id="😊-후기">😊 후기</h3>
<p>이번에 게임 프로젝트에 참여를 하였는데 웹 개발자를 지망하는 만큼 특화 프로젝트에서 다른 도메인들에 비해서는 별로 영양가는 없는 도메인이지만 이제껏 한 프로젝트 중에서는 개발 도중에 가장 재미있었고 팀 분위기도 제일 좋았던 프로젝트여서 정말 만족스러웠다. </p>
<p>또한 Java, Spring, Spring STOMP, Docker, Jenkins, nginx에 대해서 학습을 하면서 백엔드에서는 간단하게 나마 배포까지 구축할 수 있을 만큼 역량을 성장 시킬 수 있어서 얻을 것도 많았고 단순히 기능 구현을 넘어서, 처음 접하는 기술들을 학습하고 이를 실질적으로 프로젝트에 적용하는 과정에서 배우는 재미와 성취감을 느꼈다. 간단한 규모의 프로젝트였지만, 개발 중 겪은 어려움을 해결하고 팀과 함께 결과물을 만들어냈다는 점에서 매우 보람 있었다.</p>
<p>이번 프로젝트는 단순히 기술적으로 성장한 것뿐만 아니라 개발자로서의 즐거움과 협업의 중요성을 깨닫게 해준 값진 경험이었다. 앞으로도 이번 프로젝트를 통해 얻은 배움과 동기부여를 바탕으로, 더욱 깊이 있는 역량을 갖춘 웹 개발자가 되기 위해 노력하겠으며 이번에 즐겁게 같이 프로젝트를 진행한 팀원들에게 고생했다고 한마디 남기겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[싸피 2학기 공통 프로젝트(스티치)]]></title>
            <link>https://velog.io/@hk_98/%EC%8B%B8%ED%94%BC-2%ED%95%99%EA%B8%B0-%EA%B3%B5%ED%86%B5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%8A%A4%ED%8B%B0%EC%B9%98</link>
            <guid>https://velog.io/@hk_98/%EC%8B%B8%ED%94%BC-2%ED%95%99%EA%B8%B0-%EA%B3%B5%ED%86%B5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%8A%A4%ED%8B%B0%EC%B9%98</guid>
            <pubDate>Wed, 21 Aug 2024 14:13:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>프롤로그 - 2학기에 접어들며</p>
</blockquote>
<p>1학기 관통 프로젝트가 끝난 후 한달 남짓한 방학을 보낸 뒤 7월부터 팀원들을 구하여 공통 프로젝트를 시작하였다. 공통 프로젝트를 하기 앞서 방학 동안 Java 공부만 조금 해놓은 상태여서 공통 프로젝트를 잘할 수 있을지에 대한 고민들이 가득했다. </p>
<p>공통 프로젝트 팀이 만들어지고 나서 어떤 역할을 담당할지에 대한 얘기를 하면서 원래는 백엔드로 취업을 희망하고 있었고 그렇기 때문에 백엔드를 담당하고 싶기는 했지만 팀에 프론트엔드를 한다는 사람도 거의 없었고 프론트엔드도 한번 해보면 나중에 현업에서 프론트 엔드를 담당하는 분들과 협업을 할 때 원활한 소통을 할 수 있겠다고 판단하여 프론트 엔드를 담당한다고 자진해서 말을 하였고 그렇게 프론트 엔드로 공통 프로젝트를 진행하게 되었다.</p>
<br />

<blockquote>
<p>본문 - 싸피 2학기 공통 프로젝트 (스티치)</p>
</blockquote>
<h1 id="🐨steach">🐨STEACH</h1>
<hr/>


<h2 id="프로젝트-주제">프로젝트 주제</h2>
<p>취약계층 학생들과 봉사활동을 할곳이 마땅히 없는 대학생 및 일반인 분들을 위한 실시간 화상 과외 서비스</p>
<h2 id="개발-기간">개발 기간</h2>
<p><code>7월 2일 ~ 8월 16일</code></p>
<h2 id="기획-배경">기획 배경</h2>
<ul>
<li>코로나 팬데믹 이후 청소년의 교육 격차가 매우 심각한 상황이며 교육부의 교육 복지는 학교 내 지원 사업이 대부분이므로 방과 후 교육은 사각지대에 위치하고 있다.</li>
<li>만 8~23세 청소년의 16%가 한부모 및 조손 가정이고, 거주지가 반지하 혹은 옥탑 등에 해당하는 가구가 18.2%에 해당한다. 일반 청소년에 비해 취약 계층의 청소년은 방과 후 사교육 경험 비율은 43%에 불과하다.</li>
</ul>
<h2 id="목적">목적</h2>
<ul>
<li>취약 계층 학생들에게 &#39;공정&#39;한 교육 기회를 부여하여, 교육 불균형을 해소하고 진로를 위한 디딤돌을 제공한다.</li>
<li>소규모 그룹 강의를 제공하여 학생들이 집중적인 교육을 받을 수 있게 하고, 다양한 분야의 교육을 제공하여 학생들이 원하는 진로에 대해 학습할 수 있는 기회를 제공한다.</li>
</ul>
<h2 id="erd">ERD</h2>
<p><img src="https://velog.velcdn.com/images/hk_98/post/626d7738-657b-4c26-8d6d-15ae86f4b9c5/image.png" alt=""></p>
<h2 id="목업">목업</h2>
<p><img src="https://velog.velcdn.com/images/hk_98/post/fa8f792c-93bc-4b5f-931c-d068d9c0207a/image.png" alt=""></p>
<h2 id="사용-기술">사용 기술</h2>
<p><img src="https://velog.velcdn.com/images/hk_98/post/7c035060-457e-4520-9b22-5cc908540aea/image.png" alt=""></p>
<ul>
<li>백엔드 : <code>Springboot</code>, <code>Redis</code>, <code>MariaDB</code>, <code>mongoDB</code>, <code>Flask</code>, <code>Node.js</code></li>
<li>프론드엔드 : <code>React</code>, <code>TypeScript</code>, <code>Tailwind.css</code></li>
<li>인프라 : <code>AmazonEC2</code>, <code>Docker</code>, <code>Jenkins</code>, <code>NGINX</code> </li>
</ul>
<p>프론트 엔드를 담당하면서 React를 사용했던 이유로는 사용자의 편의성을 위하여 SPA 기반의 홈페이지를 구축하고 싶었으며 또한 React의 특성상 Component를 재사용할 수 있는 부분이 있기에 업무 효율성 및 디버깅 측면에서 이점이 있고 또한 React 커뮤니티의 규모가 크기 때문에 풍부한 라이브러리를 이용할 수 있는 등의 많은 이점이 있기 때문에 React를 도입하여 사용을 했다.</p>
<p>TypeScript의 경우 자바스크립트와 완벽한 호환성을 갖추고 있으며 정적인 타입을 통해 프로그램의 안정성을 높일 수 있기 때문에 도입을 했다.</p>
<p>Tailwind.css를 도입한 이유로는 inline 스타일로 개별적으로 태그 내에다 명시적으로 스타일을 입힐 수 있으며 flex 및 grid를 포함한 반응형 스타일들을 쉽게 설정할 수 있다는 장점이 있어서 도입을 했다.</p>
<h2 id="팀원-정보-및-업무-분담-내역">팀원 정보 및 업무 분담 내역</h2>
<h3 id="팀-구성">팀 구성</h3>
<p>조시현(팀장 - 백엔드), 주효림(팀원 - 백엔드), 이상철(팀원 - 백엔드), 김호경(팀원 - 인프라, 데이터), 이진송(팀원 - 프론트엔드), 김헌규(팀원 - 프론트엔드)</p>
<h3 id="내가-담당한-업무-내역">내가 담당한 업무 내역</h3>
<ul>
<li>메인 페이지 carousel 및 swiper를 이용한 디자인 및 api 통신</li>
<li>navBar 디자인 및 회원가입, 로그인, 로그아웃 링크 구현</li>
<li>검색 페이지 구현 및 navBar에 있는 검색 바를 이용한 검색 기능 구현</li>
<li>학생, 선생님 내 강의실 페이지 디자인 및 api 통신(학생 정보 CRUD, 학생 내 수업 선호도, 학생 진로 추천, 학생 커리큘럼 조회, 학생 강의 조회, 학생 강의실 입장, 선생님 정보 CRUD, 선생님 커리큘럼 조회, 선생님 강의 조회, 선생님 강의 수정, 선생님 퀴즈 CRUD)</li>
</ul>
<h2 id="기능-소개">기능 소개</h2>
<p align="center"><img src="https://velog.velcdn.com/images/hk_98/post/01827130-ecc6-4be6-be6f-c633cb154a2c/image.png" width="60%" /></p>

<p>우선 회원 가입을 할 때에는 위의 사진처럼 학생과 선생님을 나누어 회원가입을 하여 따로 관리를 하였다.</p>
<h3 id="학생">학생</h3>
<p><img src="https://velog.velcdn.com/images/hk_98/post/18d2ced4-f96f-4dee-9478-847a59233560/image.png" alt=""></p>
<p>우선 학생으로 회원가입을 하여 로그인을 하면 왼쪽에 있는 사진의 메인 페이지의 모습을 확인할 수 있는데 메인 페이지에서는 요즘 뜨는 강의와 최근 등록된 강의 그리고 광고 등의 carousel 등을 볼 수 있고 페이지 중앙에 있는 과목 아이콘을 통해 과목에 대한 검색 결과를 볼 수 있는 페이지로 이동하거나 navBar에 있는 검색 바에 검색어를 입력하거나 강의 탭을 눌러서 검색 페이지로 이동하게 되면 오른쪽 사진처럼 검색 페이지에서 검색 결과를 확인할 수 있으며 검색 조건으로 과목, 최신순, 인기순, 수강 가능 여부, 검색어 등을 이용해 검색이 가능하도록 구현하였다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/061bdc3b-e5fb-4ea5-a767-191c81bd508e/image.png" alt=""></p>
<p>학생은 검색 결과 중 하나를 선택하여 상세 페이지로 들어가게 되면 강의 요일 및 커리큘럼 등을 확인할 수 있고 학생이 수강하기로 결정하였으면 수강 신청을 하여 강의를 수강할 수 있도록 했다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/hk_98/post/ecd1e0f0-a1a1-44a4-b5a9-00eb01fe28e8/image.png
" width="60%" /></p>


<p>그 후 학생은 정해진 강의 시간에 맞춰 입장을 하게 되면 학생들은 서로의 화면과 채팅 그리고 화면 공유를 볼 수 없도록 설정하였다.(선생님의 화면 공유는 볼 수 있음.) 그 이유는 아무래도 취약 계층 학생들이 듣는 강의이다 보니 프라이버시 측면을 보호해 주기 위한 정책으로 설정하였기 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/8955be9d-d518-415f-bd37-c90fd46c9203/image.gif" alt=""></p>
<p>학생은 선생님의 강의를 듣다가 중간에 선생님이 출제한 퀴즈를 실시간으로 맞추어 점수 및 등수를 확인할 수 있도록 구현하였다. 점수는 퀴즈를 얼마나 정확하고 빨리 맞췄는지에 따라 차등적으로 지급하도록 설정하였다. 이러한 기능을 통해 학생들은 본인이 얼마나 강의를 이해하고 있으며 잘 참여하고 있는지 알게 된다. 퀴즈의 디자인은 카훗이라는 서비스를 참고하여 디자인하였다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/hk_98/post/693327e3-9e5e-4e3b-9ddc-42061aebc13e/image.png" width="60%" /></p>

<p>또한 학생이 중간에 졸고 있지는 않는지 실시간으로 감지하여 만약 학생이 졸고 있으면 졸고 있다는 알림을 띄우는 기능도 추가하였다. 이러한 기능은 이미지를 실시간으로 캡처를 하여 AI 서버로 보내게 되면 AI가 이미지를 분석하여 눈을 감고 있다고 판단을 하면 알림을 활성화하는 결과를 반환하게 되어 알림을 출력하게 되는 원리이다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/hk_98/post/699950f9-78b0-4667-bd92-840b68ee4300/image.png
" width="60%" /></p>


<p>그렇게 강의가 진행되다가 최종적으로 강의가 종료되면 학생의 퀴즈 점수 및 졸음 감지 등을 통해 계산한 집중도에 대한 정보를 학생별로 계산을 하여 서버에 보내게 되는데 학생이 강의를 들을수록 이러한 데이터가 서버에 쌓이게 되고 이러한 결과들은 학생 내 강의실 페이지의 내 정보 탭에서 나의 수업 선호도와 AI 진로 추천을 위한 데이터로 활용이 되는데 나의 수업 선호도의 경우 모든 과목에 대해 각각 100점 만점을 기준으로 과목별로 어떤 수업에 흥미도가 있는지 서버에서 기존의 강의 데이터들을 계산하여 보내주게 되고 이러한 점수들을 radar chart를 통해 시각적으로 한눈에 파악할 수 있도록 구현하였다. radar chart는 chart.js라는 라이브러리를 이용하였다. </p>
<p>AI 진로 추천 역시 수업 선호도에 사용된 점수들을 활용하여 chat GPT에게 데이터를 제공하여 진로 추천을 받을 수 있도록 하였다.  </p>
<br />

<h3 id="선생님">선생님</h3>
<p><img src="https://velog.velcdn.com/images/hk_98/post/b95338d1-1d99-46ff-8efb-787e02401051/image.png" alt=""></p>
<p>선생님 또한 학생처럼 회원가입을 하고 난 후 로그인을 하면 메인 페이지 및 검색 페이지에서 커리큘럼을 검색할 수 있다. 선생님은 navBar의 내 강의실 탭으로 이동하여 왼쪽의 메뉴바에 있는 커리큘럼 생성 버튼을 통해서 커리큘럼 생성을 할 수 있고 그 후 강의 별로 퀴즈를 생성을 하고 학생들의 수강 신청을 기다린 후 강의 날짜에 맞춰 강의를 시작하면 된다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/hk_98/post/6c6790e2-ff87-4219-b447-2dcf3e1f8f31/image.png
" width="60%" /></p>

<p>강의실에서 선생님의 경우 자신의 화면을 포함하여 최대 4명의 학생들의 화면과 공유 화면들을 볼 수 있으며 추가로 선생님이 학생별로 마이크와 화면 공유를 금지 시킬 수 있는 기능을 추가하여 선생님이 어느 정도 통제를 할 수 있도록 페이지를 추가하였다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/hk_98/post/0eccc976-dd9c-4adc-904d-eadadfc27615/image.png
" width="60%" /></p>

<p>선생님은 미리 강의별로 생성해 둔 퀴즈를 학생들에게 출제할 수 있는데 학생들이 퀴즈를 풀고 나면 학생과 똑같이 실시간으로 어떤 학생이 몇 등인지 알려주는 화면을 뜨도록 구현하였다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/hk_98/post/48d46963-2dfc-4273-888c-788be9abdd93/image.png
" width="30%" /></p>

<p>그렇게 선생님도 강의를 끝까지 진행한 후 강의 종료를 위해 강의 종료 버튼을 누르게 되면 그 강의에서의 학생들의 통계 결과와 각 학생들의 평균을 계산한 종합적인 통계 점수를 띄우도록 했고 이 모달을 통해 그 수업에서 학생들의 평균적인 데이터를 간단하게 파악할 수 있도록 구현을 했다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/hk_98/post/cf6035bb-2b73-4269-bb28-878ab3f84185/image.png
" width="50%" /></p>

<p>이러한 데이터는 종료할 때 한 번만 볼 수 있도록 하는 것이 아닌 커리큘럼 상세페이지에서 종료된 강의별로 통계 결과를 볼 수 있도록 기능을 추가하였다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/hk_98/post/27cc6a6a-baf4-4af8-a9bf-60be5d6b4bd4/image.png
" width="40%" /></p>

<p>마지막으로 선생님 봉사활동 시간의 인증의 경우는 정부 기관 및 공공 기관과 연계를 하게 될 경우 봉사활동 확인서를 발급을 해줌으로써 대학생 봉사자의 경우 봉사활동 시간을 인증할 수 있도록 봉사활동 양식을 미리 만들어 놓았다. </p>
<h3 id="프로젝트-진행-중-잘한-점">프로젝트 진행 중 잘한 점</h3>
<ul>
<li><p>Api를 따로 관리하기 위해 Api 폴더를 만들어 관리를 한 점 : 이거는 1학기 관통 프로젝트를 한 후에 다른 분의 프로젝트를 보면서 도입하고자 하는 방식이었는데 axios를 이용한 Api 통신 함수를 한곳에 모아서 관리를 하게 되면 관리하기가 좋을 듯하여 한곳에 모아서 관리를 하였다. 그 결과 이전 프로젝트에 비해서 더 깔끔하게 코드를 관리할 수 있게 되었다. </p>
</li>
<li><p>Interface를 따로 관리하기 위해 Interface 폴더를 만들어 관리를 한 점 : 이것도 Api와 동일한 이유로 프로젝트 초기에는 Interface를 그 Interface를 사용하는 파일 안에서 관리를 하였는데 그렇게 되면 api 요청을 부를 때 export를 하여 사용할 경우에 어느 파일에 어떤 Interface가 있는지 확인하기가 쉽지 않게 되어 관리가 어렵다고 판단하여 Interface를 한 곳에서 관리를 하는 게 깔끔하다고 판단하여 Interface 폴더를 따로 생성하였다.</p>
</li>
<li><p>React 학습을 꾸준히 했던 것과 프로젝트 초기보다 React 실력을 많이 늘린 점</p>
</li>
<li><p>원래는 전혀 git을 활용할 줄 몰랐는데 이번 프로젝트를 통해서 여러 git 명령어를 사용하고 많은 개념들을 익히게 된 점 : 기존에는 add, commit, push 정도만 사용할 수 있었는데 협업을 해야 한다는 강제적인 상황 덕분에 배워야겠다는 의지와 팀원들이 친절하게 알려주었기에 프로젝트를 진행하면서 fork를 이용하여 origin 레포지토리로 가져와서 branch를 생성하여 upstream 연결을 통한 pull request까지 익히게 되어 아직까지도 부족하지만 git에 대한 기본적인 사용법에 익히게 되어 많은 발전이 있었다.  </p>
</li>
</ul>
<h3 id="프로젝트-진행-중-못한-점">프로젝트 진행 중 못한 점</h3>
<ul>
<li><p>코드 컨벤션에서 정했던 변수명 처리에 대해서 잘 지키지 못한 점 : 처음에는 코드 컨벤션을 참고하여 코드를 작성했다가 시간이 지나서 일정상 바쁘게 되면서 코드 컨벤션을 잘 참고하지 않고 코드를 작성하다 보니 조금씩 코드 컨벤션을 지키지 않게 되었고 한번은 다른 팀원이 디버깅을 하면서 내가 작성한 파일명 때문에 곤란을 겪은 적이 발생하였고 그 일을 계기로 많은 반성을 하게 되었다.</p>
</li>
<li><p>slice에서 생성한 함수명과 api 파일에서 생성한 함수명을 깔끔하지 못하게 정했는 점 : 이것은 처음 코드 컨벤션을 정할 때 그때는 React에 대해 잘 모르는 것이 많았기에 이러한 부분에 대해서는 코드 컨벤션을 정하지 못하였고 그 결과 같이 프론트 엔드를 담당했던 팀원과 함수명 통일을 하지 못한 결과를 낳게 되었다.</p>
</li>
<li><p>일정적으로 많이 밀린 탓에 원래 하고 싶었던 RTC 부분과 CI/CD를 하지 못했던 점</p>
</li>
<li><p>TypeScript를 많이 공부하지 못했고 그 결과 잘 활용하지 못했던 점</p>
</li>
<li><p>일정적으로 많이 밀리게 되어서 테스트 및 성능 개선을 하지 못했던 점</p>
</li>
<li><p>폴더 및 파일 구조가 깔끔하지 못했던 점</p>
</li>
</ul>
<h3 id="프로젝트-진행-중-배웠던-것">프로젝트 진행 중 배웠던 것</h3>
<ul>
<li>React 기본 개념 및 지식</li>
<li>Tailwind 사용법</li>
<li>git 및 JIRA 사용법</li>
<li>javaScript 문법 복습 및 실력 향상</li>
<li>css 기초 개념 복습 및 활용(flex, grid)</li>
<li>백엔드에서 swagger를 잘 작성해 주면 프론트엔드 파트에서의 작업 효율이 올라간다는 것</li>
</ul>
<h3 id="프로젝트-진행-중-발생한-트러블-슈팅">프로젝트 진행 중 발생한 트러블 슈팅</h3>
<ul>
<li>Redux toolkit을 이용하여 여러 저장소로 분리한 상태에서 비동기적인 처리로 인하여 api 통신을 할 때 중요한 변수(토큰 등)들에 아무런 값이 담기지 않는 현상이 발생하여 에러가 많이 발생했었는데 이러한 원인으로 함수 내에 있는 로직들이 비동기적으로 작동을 하여 하나의 로직에서 값을 받아오기 이전에 다른 로직이 실행되었기 때문이라는 것을 알게 되었고 이를 해결하기 위하여 async await을 이용하여 함수 내의 로직을 동기적으로 처리를 했다.</li>
</ul>
<pre><code class="language-React">const handleIsUpdateInfo = async (password: string) =&gt; {
    const passwordAuthToken = await passwordCheck(password);

    localStorage.setItem(
      &quot;passwordAuthToken&quot;,
      passwordAuthToken.password_auth_token
    );

    if (passwordAuthToken) {
      setIsUpdateInfo(true);
    } else {
      console.log(&quot;에러가 발생했습니다.&quot;);
    }
  };
</code></pre>
<ul>
<li>로그인 기능을 구현하면서 처음에는 useState와 useSelector를 이용해 로그인 상태를 감지를 로직을 작성을 하였으나 로그인을 한 후 새로고침을 하게 되면 로그인 상태가 풀리게 되는 현상이 발생하게 되었고 이를 해결하기 위해 localStorage에 로그인 정보를 저장하도록 구현하였다.(후기를 작성하면서 react-persist를 이용하는 방법도 알게 되었다... 다음에는 이 방법을 사용해 보려고 한다.) </li>
</ul>
<pre><code class="language-React">
// 통합 로그인
export const loginSteach = createAsyncThunk&lt;LoginReturnForm, LoginForm&gt;(
  &quot;login&quot;,
  async (loginFormData, thunkAPI) =&gt; {
    try {
      const formDataToSend: LoginForm = {
        username: loginFormData.username,
        password: loginFormData.password,
      };

      // 로그인 API 요청
      const response = await login(formDataToSend);

      // 로컬 스토리지에 정보 저장
      localStorage.setItem(&quot;auth&quot;, JSON.stringify(response));

      return response;
    } catch (error) {
      if (axios.isAxiosError(error) &amp;&amp; error.response) {
        return thunkAPI.rejectWithValue(error.response.data);
      }
      return thunkAPI.rejectWithValue(error);
    }
  }
);
</code></pre>
<ul>
<li>검색 페이지와 navBar에서의 검색 기능을 구현을 할 때 처음에는 useState를 이용한 상태 관리를 통해서 검색 결과를 가져오려고 했으나 상태가 이전 상태로 검색이 되는 현상을 통해 상태 관리를 하기가 힘들었고 redux를 이용하여 slice를 이용해 검색 상태를 이용하려고 했으나 새로고침을 할 경우 이전에 검색했던 결과가 사라지는 현상이 발생하였고 또한 코드도 복잡하게 되어서 코드를 관리하기가 힘들겠다고 판단을 하여 다른 방법을 고민하던 와중에 팀원 중 한 명이 URL params를 이용하여 검색 기능을 구현해 보면 어떻겠냐는 조언을 듣고 params를 이용하여 검색 기능을 해결하였고 구현을 하니 우리가 흔히 많은 사이트에서 볼 수 있는 검색 URL을 볼 수 있었다.</li>
</ul>
<pre><code class="language-React">검색 페이지

  // 검색 핸들러 함수
  const handleSearch = (e: React.FormEvent | null) =&gt; {
    if (e) {
      e.preventDefault();
    }

    const newSearchOption: SearchSendCurricula = {
      ...searchOption,
      currentPageNumber: 1,
    };

    setSearchOption(newSearchOption);
    const searchParams = new URLSearchParams();
    searchParams.set(&quot;search&quot;, searchOption.search);
    searchParams.set(&quot;curriculum_category&quot;, searchOption.curriculum_category);
    searchParams.set(&quot;order&quot;, searchOption.order);
    searchParams.set(&quot;only_available&quot;, searchOption.only_available.toString());
    searchParams.set(&quot;pageSize&quot;, searchOption.pageSize.toString());
    searchParams.set(&quot;currentPageNumber&quot;, &quot;1&quot;);
    navigate(`/search?${searchParams.toString()}`);
  };

</code></pre>
<pre><code class="language-React">학생 navBar(선생님도 동일한 로직)


  // 검색 함수
  const handleSearchBar = (e: React.FormEvent) =&gt; {
    e.preventDefault();
    if (currentPath === &quot;/search&quot;) {
      const searchParams = new URLSearchParams();
      searchParams.set(&quot;curriculum_category&quot;, curriculum_category || &quot;&quot;);
      searchParams.set(&quot;search&quot;, inputSearch);
      searchParams.set(&quot;order&quot;, order || &quot;LATEST&quot;);
      searchParams.set(&quot;only_available&quot;, only_available?.toString() || &quot;false&quot;);
      searchParams.set(&quot;pageSize&quot;, searchData.pageSize.toString());
      searchParams.set(
        &quot;currentPageNumber&quot;,
        searchData.currentPageNumber.toString()
      );
      setInputSearch(&quot;&quot;);
      // 검색어와 옵션들을 URL 파라미터로 포함시켜 이동
      navigate(`/search?${searchParams.toString()}`);
    } else {
      const searchParams = new URLSearchParams();
      searchParams.set(&quot;search&quot;, inputSearch);
      searchParams.set(&quot;order&quot;, searchData.order);
      searchParams.set(&quot;only_available&quot;, searchData.only_available.toString());
      searchParams.set(&quot;pageSize&quot;, searchData.pageSize.toString());
      searchParams.set(
        &quot;currentPageNumber&quot;,
        searchData.currentPageNumber.toString()
      );
      setInputSearch(&quot;&quot;);
      // 검색어와 옵션들을 URL 파라미터로 포함시켜 이동
      navigate(`/search?${searchParams.toString()}`);
    }
  };</code></pre>
<br />

<blockquote>
<p>결론 </p>
</blockquote>
<h3 id="😊-후기">😊 후기</h3>
<p>이번 프로젝트는 지난 1학기 관통 프로젝트에 이어서 태어나서 두 번째로 하는 프로젝트였거니와 이번에는 6명이서 하는 프로젝트였기 때문에 나의 부족한 실력으로 인해 팀원들에게 피해를 주기 싫었기에 거의 매일 쉬지 않고 React 공부와 프로젝트를 하였으며 그 결과 아직 공부는 더 필요하지만 React에 대해 어느 정도 능숙하게 다룰 수 있게 되었다고 생각하여 조금이나마 수확을 얻었다고 생각한다. </p>
<p>하지만 그 와중에도 부족했던 것들이 있었는데 디자인적인 설계, 지저분한 코드, 코드 컨벤션을 잘 지키지 못했던 것, TypeScript를 제대로 공부하지 못했던 것, 테스트를 못 했던 것 등 부족한 부분이 많았었기 때문에 만족스럽지는 못했던 프로젝트 결과였다. </p>
<p>그래도 스스로 공부했던 React를 제외하고 배웠던 것들이 많았는데 1학기 때 배웠던 javaScript 문법을 복습하게 되었고 또한 더 많은 문법들을 알게 되었고 css도 flex와 grid에 대해서 배웠었지만 오랜만에 사용하다 보니 다시 개념 공부부터 시작을 하였고 프로젝트를 진행하면서 능숙하게 사용할 수 있게 되었다. 또한 프로젝트를 진행하면서 전공자 출신 싸피생 3명이 백엔드를 담당하였는데 그들이 프로젝트를 어떤 식으로 진행하였으며 비전공자인 나에게 어떻게 진행해야 하는지에 대한 것과 그 이외에도 git 사용법, swagger 사용법 등 잘 알려주었던 것들이 많아서 큰 도움이 되었다. </p>
<p>마무리로 부족한 실력으로 시작한 프로젝트였지만 답답해 하지 않으며 7주라는 시간 동안 팀적으로 불화가 없었고 잘 협력해준 우리 팀원들에게 정말로 고맙고 특히 프론트 엔드를 같이 담당했던 진송이 형에게 정말 감사의 말을 전하며 팀원 모두 싸피를 통해 좋은 곳에 취업을 하길 희망한다.</p>
<br />

<blockquote>
<p>참고</p>
</blockquote>
<p>깃허브 링크 : <a href="https://github.com/HG-KR98/steach-front">https://github.com/HG-KR98/steach-front</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[싱글톤 패턴(Singleton Pattern)]]></title>
            <link>https://velog.io/@hk_98/%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4Singleton-Pattern</link>
            <guid>https://velog.io/@hk_98/%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4Singleton-Pattern</guid>
            <pubDate>Mon, 10 Jun 2024 13:32:04 GMT</pubDate>
            <description><![CDATA[<p><span style="color:orange"><strong>싱글톤 패턴</strong></span>은 디자인 패턴 중 하나로, 애플리케이션 내에서 특정 클래스의 인스턴스가 오직 하나만 존재하도록 보장하는 패턴이다. 이러한 패턴을 사용하는 이유는 커넥션 풀, 스레드 풀, 디바이스 설정 객체 등과 같은 경우 인스턴스를 여러 개 만들게 되면 불필요한 자원을 사용하게 되고, 프로그램이 예상치 못한 결과를 낳을 수 있기 때문이다. 따라서 객체를 필요할 때마다 생성하는 것이 아닌 단 한 번만 생성하여 전역에서 이를 공유하고 사용할 수 있게 하기 위해 싱글톤 패턴을 사용한다.</p>
<br>

<h3 id="🧁-장점">🧁 장점</h3>
<ul>
<li><p>유일한 인스턴스 : 싱글톤 패턴이 적용된 클래스의 인스턴스는 애플리케이션 전역에서 단 하나만 존재하도록 보장한다. 이는 객체의 일관된 상태를 유지하고 전역에서 접근 가능하도록 한다.</p>
</li>
<li><p>메모리 절약 : 인스턴스가 단 하나뿐이므로 메모리를 절약할 수 있다. 생성자를 여러 번 호출하더라도 새로운 인스턴스를 생성하지 않아 메모리 점유 및 해제에 대한 오버헤드를 줄인다.</p>
</li>
<li><p>지연 초기화 : 인스턴스가 실제로 사용되는 시점에 생성하여 초기 비용을 줄일 수 있다.</p>
</li>
</ul>
<h3 id="🥞-단점">🥞 단점</h3>
<ul>
<li><p>결합도 증가 : 싱글톤 패턴은 전역에서 접근을 허용하기 때문에 해당 인스턴스에 의존하는 경우 결합도가 증가할 수 있다.</p>
</li>
<li><p>테스트 복잡성 : 싱글톤 패턴은 단 하나의 인스턴스만을 생성하고 자원을 공유하기 때문에 싱글톤 클래스를 의존하는 클래스는 결합도 증가로 인해 테스트가 어려울 수 있다.</p>
</li>
<li><p>상태 관리의 어려움 : 만약, 싱글톤 클래스가 상태를 가지고 있는 경우 전역에서 사용되어 변경될 수 있다. 이로 인해 예상치 못한 동작이 발생할 수 있다.</p>
</li>
<li><p>전역에서 접근 가능 : 애플리케이션 내 어디서든 접근이 가능한 경우, 무분별한 사용을 막기 힘들다. 이로 인핸 변경에 대한 복잡성이 증가할 수 있다.</p>
</li>
</ul>
<pre><code class="language-java">public class Singleton {
    // 정적 멤버 변수로 유일한 인스턴스를 가지도록 함
    private static Singleton instance;

    // 생성자를 private으로 선언하여 외부에서 인스턴스를 생성하지 못하도록 함
    private Singleton() {
        // 생성자 내용
    }

    // 외부에서 유일한 인스턴스에 접근할 수 있는 정적 메서드 제공
    public static Singleton getInstance() {
        // 인스턴스가 생성되지 않았을 경우에만 생성하도록 함
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}</code></pre>
<br>

<p>싱글톤 패턴은 자주 사용되는 디자인 패턴 중 하나이지만, 과용될 경우 전역 상태로 인한 문제가 발생할 수 있으므로 주의해야 한다. 또한 멀티스레드 환경에서 스레드 안전한 방식으로 구현되어야 하며, 이를 위해 다양한 방법으로 싱글톤 인스턴스의 생성 및 접근을 동기화할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hk_98/post/95e382bc-b3b4-48aa-bbeb-82ce9fe1d7f7/image.png" alt=""></p>
<hr>

<blockquote>
<p><span style="color : skyblue"><strong>참고</strong></span>
<a href="https://ittrue.tistory.com/563">https://ittrue.tistory.com/563</a> </p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>