<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>tori.log</title>
        <link>https://velog.io/</link>
        <description>인사이트를 얻고 정리하는 공간입니다</description>
        <lastBuildDate>Sun, 08 Feb 2026 08:09:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>tori.log</title>
            <url>https://velog.velcdn.com/images/jae-jang/profile/3de2cfeb-c689-4572-81df-36c6a0cbdda4/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. tori.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jae-jang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[엑셀 설정]]></title>
            <link>https://velog.io/@jae-jang/%EC%97%91%EC%85%80-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@jae-jang/%EC%97%91%EC%85%80-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sun, 08 Feb 2026 08:09:13 GMT</pubDate>
            <description><![CDATA[<h3 id="1-하이퍼링크-제거하기">1. 하이퍼링크 제거하기</h3>
<blockquote>
<p>파일 &gt; 옵션 &gt; 언어교정 &gt; 자동고침 옵션 &gt; 입력할 때 자동 서식 탭</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/6b54605a-2bdc-48f8-af14-c22d51e12233/image.png" alt=""></p>
<ul>
<li>해당 부분 해제시 이메일 주소 같은거 입력할 때 하이퍼 링크가 적용되지 않는다.</li>
</ul>
<h3 id="2-한영-자동-변환--영어-첫글자-대문자-제거하기">2. 한영 자동 변환 &amp; 영어 첫글자 대문자 제거하기</h3>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/95032b63-f9a6-42cd-a551-3b44e69d5938/image.png" alt=""></p>
<ul>
<li>사진상의 부분을 비활성화 해주자 </li>
</ul>
<h3 id="3-명령어-추가하기-오름차순-내림차순-병합">3. 명령어 추가하기 (오름차순, 내림차순, 병합)</h3>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/85375425-5e86-49f8-ac37-9a06e87ec1c2/image.png" alt=""></p>
<ul>
<li>빠른 실행 도구를 추가해주자 </li>
</ul>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/0cb98cac-dab4-44b4-842d-e0c826fa83e0/image.png" alt=""></p>
<ul>
<li>빠른 실행 도구 모음에서 많이 사용하는 명령어를 다음과 같이 추가해주자</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/0b9fcb3b-f6ab-4b6d-87e3-d1d9b8936f4f/image.png" alt=""></p>
<ul>
<li>내가 추가해준 명령어는 <code>alt</code> 를 클릭해서 쉽게 활용할 수 있다. 예로 들어서 1번이 오름차순인 경우, 인구수에 하나의 셀에서 <code>alt + 1</code> 만으로 바로 정렬이 되는 것이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Enum 사용법 | 최상위문 | Null 병합 연산]]></title>
            <link>https://velog.io/@jae-jang/Enum-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@jae-jang/Enum-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Sat, 27 Dec 2025 05:47:57 GMT</pubDate>
            <description><![CDATA[<h3 id="enum-사용법">Enum 사용법</h3>
<pre><code class="language-c">class Program
{
    static void Main(String[] args)
    {
        Days restDay = Days.Monday;

        if (restDay == Days.Sunday)
        {
            Console.WriteLine(&quot;일요일 입니당&quot;);
        }
        else
        {
            Console.WriteLine(&quot;일요일 아님&quot;);
        }
        Console.ReadKey();
    }
}

enum Days
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}</code></pre>
<hr>
<h3 id="최상위문">최상위문</h3>
<p><code>C#</code> 에서 최상위문이란 <code>Main</code> 을 생략해도 자동으로 만들어준다는 뜻이다.</p>
<p><code>Main</code> 문은 아래 예제와 같이 <code>Class</code> 내부에 위치한다.</p>
<pre><code class="language-c">class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(args.Length);
    }
}</code></pre>
<p>그런데 아래의 예제 코드가 위의 코드와 완벽하게 동일하다.</p>
<pre><code class="language-c">Console.WriteLine(args.Length);</code></pre>
<p>그 이유는 컴파일러가 자동으로 <code>Main</code> 문을 만들어주기 때문이다. 이러한 <code>C#</code> 의 문법을 최상위문이라고 부르며, 실무에서는 사용하지는 않을 것 같다. 단순히 테스트 용도 정도로는 유용하게 사용되지 않을까?</p>
<hr>
<h3 id="null-병합-연산">Null 병합 연산</h3>
<p><code>C#</code> 에서는 <code>int a = null</code> 코드가 에러가 난다. 왜냐하면 <code>int</code> 값에 <code>null</code> 을 허용하지 않기 때문이다. 그렇기 때문에 <code>null</code> 값을 <code>int</code> 에 받고 싶다면 아래와 같이 코드를 작성한다.</p>
<pre><code class="language-c">//모두 같은 의미임
Nullable&lt;int&gt; a = null;
int? a = null; </code></pre>
<p>개발을 할 때 의미가 중요하기 때문에 <code>Nullable&lt;int&gt; a = null</code> 방식으로 작성하는 것이 더욱 명시적이라서 해당 방식을 많이 활용할 것 같다.</p>
<p>해당 <code>Nullable</code> 객체는 아래와 같이 정의되어 있다. 아직 모든 메서드를 잘 알지는 못하지만 적어도 <code>HasValue</code> 메서드는 자주 사용할 것 같다.</p>
<pre><code class="language-c">public Nullable(T value)
{
    public readonly bool HasValue { get; } 
    public readonly T HasValue { get; }
    public override bool Equals(object? other);
    public override int GetHashCode();
    public readonly T GetValueOrDefault();
    ...
}</code></pre>
<p>마지막으로 <code>Nullable</code> 객체를 활용하여 <code>Null 병합 연산</code> 은 다음과 같다.</p>
<pre><code class="language-c">int? a = null; 
int? b = null;

// b가 null이면 a에 3을 대입해라. b가 null이 아니면 b값을 a에 대입
a = b ?? 3; 

Console.WriteLine(&quot;a: &quot; + a); // 3
Console.WriteLine(&quot;b: &quot; + b); // null</code></pre>
<hr>
<pre><code class="language-c">int? a = null; 
int? b = 3;

a ??= b;  //a가 null이면 b 값을 할당해라

Console.WriteLine(&quot;a: &quot; + a); //3
Console.WriteLine(&quot;b: &quot; + b); //3
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[다형성2: 추상 클래스 VS 인터페이스]]></title>
            <link>https://velog.io/@jae-jang/%EB%8B%A4%ED%98%95%EC%84%B12-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-VS-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@jae-jang/%EB%8B%A4%ED%98%95%EC%84%B12-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-VS-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Sun, 28 Sep 2025 12:35:10 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>좋은 객체 지향 설계 원칙중 하나인 OCP 원칙은 다형성인 인터페이스, 추상 클래스 등을 이용하여 준수할 수 있다. 이때 구현 VS 상속을 확실히 구분하여 언제 무엇을 사용하는 것이 좋을지에 대해서 감을 잡아보자.</p>
<blockquote>
<p>OCP 는 코드 기능의 확장에는 열려 있고, 코드 수정시 최대한 기존의 코드의 변경을 줄이는 것이다.</p>
</blockquote>
<h2 id="추상-클래스">추상 클래스</h2>
<p>추상 클래스는 왜 만들어졌을까? 본격적인 논의 전에 아래의 그림을 살펴보자.
<img src="https://velog.velcdn.com/images/jae-jang/post/f1610e44-633e-4512-96dc-bcfe83cddbd7/image.png" alt=""></p>
<ul>
<li>전형적인 상속 구조이다. <code>Animal</code> 의 <code>sound</code> 메서드를 3개의 자식 클래스가 상속받고 있다. 그런데 <code>Animal</code> 클래스가 생성하여 활용하는 것이 의미가 있을까?<pre><code class="language-java">Animal animal = new Animal(); //이거 쓸 수 있어??</code></pre>
</li>
<li>의미 없다. 따라서 이러한 <code>Animal</code> 은 <strong>생성 용도가 아닌, 상속 용도로만 활용</strong>하는 클래스 라는 것을 알려주기 위해서 추상 클래스 개념이 도입된 것이다.</li>
</ul>
<pre><code class="language-java">public abstract class Animal{
    public void sound() {print(&quot;동물이 웁니다.&quot;)} //상속 기능 제공
    public abstract void move(); //모든 자식 클래스가 의무적으로 오버라이딩 해야함 
}</code></pre>
<h2 id="인터페이스">인터페이스</h2>
<pre><code class="language-java">//순수 추상 클래스
public abstract class AbstractAnimal {
   public abstract void sound();
   public abstract void move();
}

//인터페이스 
public interface InterfaceAnimal {
   void sound();
   void move();
}</code></pre>
<ul>
<li>위의 두 클래스는 동일한 역할을 수행한다. 인터페이스에서는 <code>public abstract</code> 키워드를 생략할 수 있어서 간편하다.</li>
</ul>
<h3 id="인터페이스는-다중-구현이-가능하다">인터페이스는 다중 구현이 가능하다.</h3>
<pre><code class="language-java">public interface InterfaceA {
    void methodA();
    void methodCommon(); //공통 메서드임
}
public interface InterfaceB {
    void methodB();
    void methodCommon(); //공통 메서드임
}
public class Child implements InterfaceA, InterfaceB{
    @Override
    public void methodA(){print(&quot;Child.methodA&quot;)}
    @Override
    public void methodA(){print(&quot;Child.methodB&quot;)}
    @Override
    public void methodCommmon(){print(&quot;child.methodCommon&quot;)} //하나만 구현
}
Interface a = new Child();
a.methodCommon();
//a.methodB(); //요고는 안됨 InterfaceB를 통해서 접근</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/090a89be-5873-41b0-a98f-a0742e4e3bda/image.png" alt=""></p>
<ul>
<li>인터페이스에서 다중구현이 허용되는 이유는 공통되는 메서드는 한번만 구현하면 되기 때문이다. 상속의 다중구현인 경우 공통된 메서드를 자식 클래스 입장에서 어떻게 받아들어야 하는지 혼란이 와서 안되지만, 반면에 인터페이스는 가능하다.</li>
</ul>
<h2 id="상속-vs-구현">상속 vs 구현</h2>
<p>부모 클래스의 기능을 자식이 물려받을 때는 상속, 인터페이스는 구현한다는 표현을 한다. 이들을 구분하는 기준은 <strong>부모의 기능을 물려주는지에 대한 여부</strong>이다. 추상클래스는 자식 클래스에게 본인의 기능을 물려줄 수 있다. 반면에 인터페이스는 구현해야 되는 메서드를 강제한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[다형성의 이해1: 다형적 참조, 오버라이딩]]></title>
            <link>https://velog.io/@jae-jang/%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%98-%EC%9D%B4%ED%95%B41-%EB%8B%A4%ED%98%95%EC%A0%81-%EC%B0%B8%EC%A1%B0-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9</link>
            <guid>https://velog.io/@jae-jang/%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%98-%EC%9D%B4%ED%95%B41-%EB%8B%A4%ED%98%95%EC%A0%81-%EC%B0%B8%EC%A1%B0-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9</guid>
            <pubDate>Sun, 28 Sep 2025 12:09:05 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>객체 지향 프로그래밍에서 제공하는 <strong>다형성</strong> 에 대해서 살펴보자. 다형성은 공통 부분(메서드, 변수)을 자식 클래스에게 상속해주거나, 역할과 구현의 기능을 분리하는 강력한 기능을 제공한다. 이를 이해하기 위해서는 <strong>다형적 참조와 메서드 오버라이딩</strong>을 이해해야 한다.</p>
<h2 id="1-다형적-참조">1. 다형적 참조</h2>
<p>다형적 참조란 자바에서 부모 타입은 자신은 물론, 자신을 기준으로 모든 자식 타입을 참조할 수 있다는 뜻이다.</p>
<pre><code class="language-java">//다형적 참조 예시
Parent poly = new Child(); //이게 다형적 참조임
//Child  child   = new Parent(); //이건 안됨

public Class Parent{
    public void parentMethod(){
        print(&quot;parentMethod&quot;)
    }
}

public Class Child extends Parent{
    public void childMethod(){
        print(&quot;childMethod&quot;)
    }
}</code></pre>
<p>이때 <code>Parent poly = new Child()</code> 를 유심히 살펴보자. 이러한 <code>poly</code> 가 생성되면 아래와 같이 인스턴스가 만들어진다.
<img src="https://velog.velcdn.com/images/jae-jang/post/a85dc3f1-f14c-40f7-81ff-4496dc0f802e/image.png" alt=""></p>
<ul>
<li><p>인스턴스 내부에 <code>Parent</code> <code>Child</code> 모두 생성되지만 <code>poly</code> 는 부모 타입을 가르키기 때문에 <code>childMethod</code> 의 접근이 힘들다.</p>
</li>
<li><p>이러한 이유로 다형적 참조를 활용할 때 캐스팅이 언급된다. <code>poly</code> 를 다운 캐스팅하면 <code>childMethod</code> 접근이 가능하기 때문이다.</p>
</li>
</ul>
<pre><code class="language-java">Child child = (Child) poly;
child.childMethod();</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/e1482cef-b699-4b38-ade6-0b1f8abdd4cd/image.png" alt=""></p>
<h3 id="캐스팅시-주의점">캐스팅시 주의점</h3>
<p>캐스팅인 업, 다운 2가지 종류가 있다. 자바에서는 업캐스팅은 <code>Parent poly = new Child()</code> 처럼 다형적 참조를 활용할 수 있기에 오히려 권장된다.
다만 다운 캐스팅은 런타임 에러가 발생할 수 있기 때문에 명시적으로 <code>(Child) poly</code> 처럼 작성해야 한다. 참고로 <code>instanceof</code> 키워드를 활용하여 캐스팅 가능 여부를 체크하는 것이 매우 중요하다. 아래의 코드를 살펴보자</p>
<pre><code class="language-java">Parent parent = new Parent();
Child child = (Child) parent;
child.childMethod(); //런타임 에러 발생</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/c0bbe58d-48cc-42b6-94a9-8aeeb686f3eb/image.png" alt=""></p>
<ul>
<li><code>parent</code> 의 인스턴스 내부에는 <code>child</code> 인스턴스를 포함하지 않아서 런타임 에러가 발생한다. 이러한 이유로 <code>instanceof</code> 함수를 활용하여 참조하는 인스턴스 타입을 확인해야 한다.<pre><code class="language-java">Parent parent = new Parent();
// parent 가 Child 인스턴스를 참조하는 경우
if (parent instance of Child){
  Child child = (Child) parent; //다운 케스팅
  child.childMethod();
}</code></pre>
</li>
</ul>
<h2 id="2-메서드-오버라이딩">2. 메서드 오버라이딩</h2>
<p>앞서 상속파트에서 일부 오버라이딩 메모리 구조를 파악했지만, 이번 장에서는 다형적 참조 시 메서드 오버라이딩의 동작 방식을 집중적으로 살펴보자.</p>
<pre><code class="language-java">public class Parent {
    String value = &quot;parent&quot;
    public void method() {
        print(&quot;parentMethod&quot;)
    }
}

public class Child extends Parent {
    String value = &quot;child&quot;

    @Override
    public void method() {
        print(&quot;childMethod&quot;)
    }    
}

Parent poly = new Child();
poly.method(); //부모와 자식 메서드 중 뭐가 실행될까?</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/75d4527f-51dc-49af-ac71-7e2e5d452209/image.png" alt=""></p>
<ul>
<li><code>poly</code> 는 먼저 <code>Parent method</code> 에 접근하지만, 자식 클래스에 오버라이딩한 <code>method</code> 가 있으면 그 메서드가 우선순위를 가진다. 따라서 위 코드에서는 <code>childMethod</code> 가 프린티 된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 상속 시 메모리 구조]]></title>
            <link>https://velog.io/@jae-jang/%EC%9E%90%EB%B0%94-%EC%83%81%EC%86%8D-%EC%8B%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@jae-jang/%EC%9E%90%EB%B0%94-%EC%83%81%EC%86%8D-%EC%8B%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sat, 27 Sep 2025 06:01:16 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>자바에서 제공하는 상속의 기능은 공통부분을 부모에서 제공함으로써 유지보수의 용이함에 도움을 준다. 이러한 상속을 이해하기 위해서 힙 영역에서 <strong>인스턴스가 어떻게 생성되는지</strong> 이해할 필요가 있다.</p>
<h2 id="상속-시-메모리-구조">상속 시 메모리 구조</h2>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/5f29590e-a206-4873-9c0f-8af896b766be/image.png" alt=""></p>
<ul>
<li><p>그림과 같이 <code>Car</code> 라는 부모 객체를 2개의 자식이 상속받고 있다. 이 덕분에 전기차와 가스차에서 모두 <code>move</code> 의 기능을 공통적으로 활용할 수 있게 된다.</p>
</li>
<li><p>이때 <code>ElecticCar</code>를 생성할 때 메모리 구조는 어떻게 될까?</p>
<pre><code class="language-java">public class Car{
  public void move(){
      print(&quot;차가 움직입니다.&quot;)
  }
}
</code></pre>
</li>
</ul>
<p>public class EletricCar extends Car{
    public void charge(){
        print(&quot;충전합니다.&quot;)
    }
}</p>
<p>EletricCar electricCar = new EletricCar();</p>
<pre><code>![](https://velog.velcdn.com/images/jae-jang/post/9021c88d-9c16-4517-a214-8f5d42b1d9ab/image.png)

- 정답은 **부모의 인스턴스와 자식의 인스턴스가 동시에 생성**이 된다. 

- 이러한 이유로 부모 클래스를 상속받을 때, 자식 클래스의 생성자에서 부모 클래스의 생성자인 `super` 를 꼭 기입해줘야 한다. (없으면 부모의 기본 생성자를 JVM이 자동으로 실행해줘서 오류가 발생하지 않은 것이다)

## 자식 클래스에서 부모 메서드 호출하기

![](https://velog.velcdn.com/images/jae-jang/post/712783f0-95ec-48b6-9b80-7a1e7dc79192/image.png)

- `electricCar.move` 호출 시

- 본인 타입 (전기차) 를 기준으로 `move` 를 찾고, 만약에 없다면 부모 클래스에서 찾는다. 부모 클래스에도 없다면 상위 부모 클래스로 접근하여 계속 `move` 클래스를 찾는다.



</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 조합, 순열]]></title>
            <link>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A1%B0%ED%95%A9-%EC%88%9C%EC%97%B4</link>
            <guid>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A1%B0%ED%95%A9-%EC%88%9C%EC%97%B4</guid>
            <pubDate>Tue, 16 Sep 2025 02:15:53 GMT</pubDate>
            <description><![CDATA[<h2 id="중복-순열">중복 순열</h2>
<p>기호로 <code>n_ㅠ_r</code> 이고 연산 결과는 <code>n ** r</code> 이다. 이는 n개의 서로 다른 원소들을 중복을 허락하여 r개 뽑은 후 배치하는 것이다.</p>
<blockquote>
<pre><code class="language-python">from itertools import product
for a in product([1, 2, 3], repeat=2):
    print(a)</code></pre>
</blockquote>
<p>```</p>
<ul>
<li>연산 결과는 (1, 1), (1, 2), (1, 3) ... (3, 3) 으로 총 3 ** 2 인 9가지가 나온다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL WITH]]></title>
            <link>https://velog.io/@jae-jang/SQL-WITH</link>
            <guid>https://velog.io/@jae-jang/SQL-WITH</guid>
            <pubDate>Wed, 10 Sep 2025 11:46:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>with</code> 문을 사용하면 테이블을 <strong>커스터 마이징</strong>하여 사용할 수 있다</p>
</blockquote>
<h3 id="예제">예제</h3>
<pre><code class="language-sql">WITH counter AS (
    SELECT h.hacker_id, h.name, count(*) AS challenges_created
    FROM Hackers h
        INNER JOIN Challenges C ON h.hacker_id = C.hacker_id
    GROUP BY h.hacker_id, h.name
)
SELECT hacker_id, name, challenges_created
FROM counter</code></pre>
<ul>
<li><p><code>with counter</code> 를 통해 <code>counter</code> 라는 임시 테이블을 만들어주었다.</p>
</li>
<li><p>이를 활용하여 반복되는 서브쿼리를 획기적으로 줄일 수 있다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 컬렉션]]></title>
            <link>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%BB%AC%EB%A0%89%EC%85%98</link>
            <guid>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%BB%AC%EB%A0%89%EC%85%98</guid>
            <pubDate>Wed, 06 Aug 2025 07:14:55 GMT</pubDate>
            <description><![CDATA[<h3 id="counter">Counter</h3>
<pre><code class="language-python">from collections import Counter

ex_list = [&#39;kim&#39;, &#39;kim&#39;, &#39;park&#39;, &#39;choi&#39;, &#39;kim&#39;, &#39;kim&#39;, &#39;kim&#39;, &#39;choi&#39;, &#39;park&#39;, &#39;choi&#39;]
ex_counter = Counter(ex_list)
print(ex_counter) #Counter({&#39;kim&#39;: 5, &#39;choi&#39;: 3, &#39;park&#39;: 2})
print(dict(ex_counter)) #{&#39;kim&#39;: 5, &#39;park&#39;: 2, &#39;choi&#39;: 3}</code></pre>
<ul>
<li><code>Counter</code> 는 자료구조는 아니지만 반복되는 데이터를 카운터하여 딕셔너리 형식으로 반환해주는 컬렉션이다.</li>
<li>더 많은 기능은 <a href="https://velog.io/@kimdukbae/Python-collections-%EB%AA%A8%EB%93%88%EC%9D%98-Counter#%EB%94%95%EC%85%94%EB%84%88%EB%A6%ACdictionary">여기</a> 참고</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 리스트]]></title>
            <link>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@jae-jang/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Wed, 06 Aug 2025 07:11:34 GMT</pubDate>
            <description><![CDATA[<h3 id="1-extend">1. extend</h3>
<pre><code class="language-python">x = [1, 2, 3]
y = [4, 5]
x.extend(y)
print(x) #[1, 2, 3, 4, 5]</code></pre>
<ul>
<li>리스트의 <code>extend</code> 를 적용하면 iterable 하게 펼쳐서 데이터를 추가해준다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL case]]></title>
            <link>https://velog.io/@jae-jang/SQL-case</link>
            <guid>https://velog.io/@jae-jang/SQL-case</guid>
            <pubDate>Thu, 10 Jul 2025 12:21:55 GMT</pubDate>
            <description><![CDATA[<h2 id="case-기초">CASE 기초</h2>
<h4 id="초기-테이블">초기 테이블</h4>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/1b6431c9-cc1f-4aaa-a877-a355ab181640/image.png" alt=""></p>
<h4 id="simple-ex">simple ex</h4>
<pre><code class="language-sql">SELECT CASE
            WHEN categoryid = 1 THEN &#39;음료&#39;
            WHEN categoryid = 2 THEN &#39;조미료&#39;
            ELSE &#39;기타&#39;
       END AS new_category, categoryid
FROM Products</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/fb580355-2048-46c9-96ce-87da89096b32/image.png" alt=""></p>
<h4 id="case-with-group-by">case with group by</h4>
<pre><code class="language-sql">SELECT CASE
            WHEN categoryid = 1 THEN &#39;음료&#39;
            WHEN categoryid = 2 THEN &#39;조미료&#39;
            ELSE &#39;기타&#39;
       END AS new_category, AVG(price)
FROM Products
GROUP BY new_category</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/f4d86df0-d402-4a6c-9000-4316963280a1/image.png" alt=""></p>
<h2 id="피보팅">피보팅</h2>
<p>피보팅은 테이블의 데이터를 row 형태로 보여줄 때 주로 사용하곤 한다. 아래 쿼리를 기반으로 한 테이블을 살펴보자.</p>
<pre><code class="language-sql">SELECT AVG(CASE WHEN categoryid = 1 THEN price ELSE NULL END) AS category1_avg_price
      ,AVG(CASE WHEN categoryid = 2 THEN price ELSE NULL END) AS category2_avg_price
      ,AVG(CASE WHEN categoryid = 3 THEN price ELSE NULL END) AS category3_avg_price
FROM Products</code></pre>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/eec41c21-1211-4eac-b5dd-b91a11427569/image.png" alt=""></p>
<ul>
<li>각각의 <code>case</code> <code>end</code> 문은 원본 <code>Products</code> 테이블에서 조건에 따른 데이터를 추출하여 <strong><code>row</code>로 보여준다</strong>는 것을 알 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 작동 원리]]></title>
            <link>https://velog.io/@jae-jang/Spring-Security-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@jae-jang/Spring-Security-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Tue, 03 Jun 2025 08:53:59 GMT</pubDate>
            <description><![CDATA[<h3 id="spring-security의-큰-틀에-대해서는-아래와-같다">spring security의 큰 틀에 대해서는 아래와 같다.</h3>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/4c5f8874-dc65-4ae8-a818-c6fcaff519ab/image.png" alt=""></p>
<ol>
<li><p>사용자가 <code>user/login</code> 등의 경로를 통해 로그인 요청을 한다.</p>
</li>
<li><p><code>UsernamePasswordAuthenticationFilter</code> 가 해당 요청을 가로챈다. 그리고 이 요청에서 사용자가 전송한 <code>id</code> <code>password</code> 를 꺼내 <code>UsernamePasswordAuthenticationToken</code> 을 만든다. 이 토큰은 단순히 <code>Spring Security</code> 프레임워크의 로그인 기능을 사용하기 위해서 생성해야 되는 객체이다.</p>
<pre><code class="language-java">public class LoginFilter extends UsernamePasswordAuthenticationFilter {

 private final AuthenticationManager authenticationManager;

 @Override
 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

     //클라이언트 요청에서 username, password를 가로챔
     try {
         ...

         //사용자가 아이디나 비밀번호를 입력하지 않은 경우
         if (userDto.getUsername() == null || userDto.getPassword() == null) {
             ResponseUtil.setErrorResponse(INVALID_LOGIN_PARAMETER.getCode(), response, INVALID_LOGIN_PARAMETER.getMessage());
             return null;
         }

         //spring security 전용 로그인 포맷 생성
         UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDto.getUsername(),
                 userDto.getPassword(), null);
         //authenticationManager에 사용자 아이디, 비밀번호 정보 넘기기 -&gt; authManager가 정보를 userDetail에 전달한다.
         return authenticationManager.authenticate(authToken);
     } catch (IOException e) {
         throw new RuntimeException(e);
     }
 }</code></pre>
</li>
<li><p>위의 코드에서 <code>return authenticationManager.authenticate(authToken);</code> 를 실행하여 <code>AuthenticationManager</code> 에 토큰 정보를 전달한다.</p>
</li>
<li><p><code>AuthenticationManager</code> 는 토큰 정보를 토대로 어떠한 <code>Provider</code> 에게 로그인을 맡길 것인지 결정한다. 예로 들어서 아이디, 패스워드 인증이 필요한 경우에는 <code>DaoAuthenticationProvider</code> 를 선택하고, 소셜 로그인을 하는 경우 <code>OAuth</code> 방식을 사용하는 <code>OAuth2LoginAuthenticationProvider</code> 를 선택한다.</p>
</li>
</ol>
<p>5 ~ 8. 위의 과정에서 선택된 <code>Provider</code>가 <code>UserDetailsService</code> 를 호출한다. <code>UserDetailsService</code> 는 <code>DB</code> 에서 사용자 정보를 꺼내오는 역할을 한다. 그리고 이 정보를 다시 <code>Provider</code> 에 전달하고 <code>Provider</code>는 실질적인 로그인 인증을 수행한다.</p>
<pre><code class="language-java">//UserDetailsService 예제 코드
public class CustomUserDetailService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User userData = userRepository.findByUsername(username)
                .orElseThrow(() -&gt; new UserException(ResponseCode.INVALID_USER));

        if (userData != null) {
            return new CustomUserDetails(userData);
        }

        return null;
    }
}</code></pre>
<ul>
<li><code>return new CustomUserDetails(userData);</code> 는 <code>Provider</code> 에 사용자 정보를 넘겨주는 코드이다.</li>
</ul>
<p>9 ~ 11. 사용자 로그인에 성공하였다면, 해당 정보를 <code>SecurityContextHolder</code> 에 저장한다. 나는 JWT를 사용하기 때문에 토큰이 들어온다면, 유효성 검정을 하고 <code>SecurityContextHolder</code>에 저장하는 방식을 채택했다.</p>
<pre><code class="language-java">public class JWTFilter extends OncePerRequestFilter {

    private final JWTUtil jwtUtil;

    //토큰 인증 정보를 security context에 저장 (사용자 이름, 역할 등을 잠시 저장하려고)
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String jwt = jwtUtil.resolveToken(httpServletRequest);

        try {
            //토큰이 유효 하다면
            if (StringUtils.hasText(jwt) &amp;&amp; jwtUtil.isValidToken(jwt)) {
                String username = jwtUtil.getUsername(jwt);
                String role = jwtUtil.getRole(jwt);
                User user = new User(username, role);
                //UserDetails 에 회원 정보 객체 담기
                CustomUserDetails customUserDetails = new CustomUserDetails(user);
                //스프링 시큐리티 인증 토큰 생성
                Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
                //세션에 사용자 등록
                SecurityContextHolder.getContext().setAuthentication(authToken);
//                filterChain.doFilter(request, response);
            }

            filterChain.doFilter(request, response); //토큰이 필요없는 경우 일수도 있으므로 doFilter 수행

        } ...
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[2차원 배열 뒤집기]]></title>
            <link>https://velog.io/@jae-jang/2%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4-%EB%92%A4%EC%A7%91%EA%B8%B0</link>
            <guid>https://velog.io/@jae-jang/2%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4-%EB%92%A4%EC%A7%91%EA%B8%B0</guid>
            <pubDate>Thu, 01 May 2025 07:08:23 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-python"># 2차원 리스트 90도 회전
def rotate_a_matrix_by_90_degree(a):

    n = len(a) # 행의 길이 
    m = len(a[0]) # 열의 길이
    result = [[0] * n for _ in range(m)]

    for i in range(n):
        for j in range(m):
            result[j][n - i - 1] = a[i][j]

    return result</code></pre>
<pre><code class="language-python">#2차원 배열 대칭
arr = arr[::-1] #상하 대칭

for i in range(n): #n은 행의 크기
    arr[i] = arr[i][::-1] #좌우 대칭</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSL 작동 방식]]></title>
            <link>https://velog.io/@jae-jang/SSL-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@jae-jang/SSL-%EC%9E%91%EB%8F%99-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Tue, 01 Apr 2025 12:36:54 GMT</pubDate>
            <description><![CDATA[<h4 id="서론">서론</h4>
<p>사용자에게 안전한 웹을 제공하기 위해서 생각해야 할 요소는 바로 <strong>보안</strong>이다. 이때 사용자의 데이터를 주고 받을 때 암호화를 진행하는 SSL을 고려할 수 있을 것이다. SSL을 적용하면 <code>http</code> -&gt; <code>https</code> 가 되며, 주고받는 데이터에 대해서 암호화가 진행된다. 구체적으로 이러한 SSL 인증서를 <code>CA</code> 라는 신뢰할 수 있는 구간에서 발급을 받으며, 이 발급된 인증서를 서버에 삽입하면 된다.</p>
<h3 id="ssl-구동-원리">SSL 구동 원리</h3>
<img src="https://velog.velcdn.com/images/jae-jang/post/ee37f72d-ba57-450c-939b-8cdc0df42b52/image.png" width="80%" height="n%">

<ul>
<li><p>사용자가 크롬 웹 브라우저를 이용해서 특정 웹 사이트에 접속한다고 가정해보자.</p>
</li>
<li><p>그러면 사용자가 특정 웹 브라우저를 클릭했을때 먼저 크롬 브라우저가 해당 웹 사이트가 SSL 인증서가 있는지 확인하다.</p>
</li>
<li><p>그 후에 만약 SSL 인증서가 있으면 안전하다는 OK 사인을 사용자에게 보낸다.</p>
<img src="https://velog.velcdn.com/images/jae-jang/post/fbed83c2-aab3-4097-8445-d013defb58e8/image.png" width="60%" height="50%">
</li>
<li><p>사용자는 서버에게 공개키를 우선 요청한다. 그러고 나면 서버는 사용자에게 공개키를 전달한다.</p>
</li>
<li><p>사용자는 전달 받은 공개키를 기반으로, 대칭키를 암호화하여 생성하고 서버에 전송한다.</p>
</li>
<li><p>이 암호화된 대칭키를 전달 받은 서버는 본인의 비밀키를 이용하여 복호화하고 대칭키를 얻는다.</p>
</li>
<li><p>사용자의 세션이 끊길때까지 초기에 서로 주고 받은 대칭키로 데이터를 주고 받게 된다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[XSS CSRF]]></title>
            <link>https://velog.io/@jae-jang/XSS-CSRF</link>
            <guid>https://velog.io/@jae-jang/XSS-CSRF</guid>
            <pubDate>Thu, 27 Mar 2025 04:12:48 GMT</pubDate>
            <description><![CDATA[<h2 id="xss">XSS</h2>
<p>XSS는 <strong>웹 게시판이나 메일 등에 자바 스크립트와 같은 스크립트 코드를 삽입</strong>해 개발자가 고려하지 않은 기능이 작동하게 하는 공격이다. 주로 사용자의 로그인 상태를 기록하기 위해, 쿠키나 로컬 스토리지에 정보를 저장하는데, 이를 가져올 수 있다.</p>
<h4 id="xss-절차">XSS 절차</h4>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/1bb73566-65d8-49a3-b5c0-a8d4f17d0d96/image.png" alt=""></p>
<ul>
<li><p>해커가 웹사이트의 게시판에 악의적인 스크립트를 삽입</p>
</li>
<li><p>사용자가 해커의 스크립트가 삽입된 게시판을 읽음</p>
</li>
<li><p>사용자의 정보가 해커로 유출됨</p>
</li>
</ul>
<h4 id="xss-해결방법">XSS 해결방법</h4>
<ol>
<li><p>만약에 쿠키에 정보를 저장한다면 HttpOnly를 설정한다. (xss에는 방어가 완벽하게 되지만, csrf에 취약해짐)</p>
</li>
<li><p>근본적으로 xss 공격은 입력값에 대한 검증이 제대로 이루어지지 않아 발생하는 취약점이다. 따라서 사용자의 모든 입력값에 대하여 필터링을 해주어야 한다. ex) &lt;, &gt;, &quot;, &#39;등 주로 스크립트를 실행하기 위한 특수문자를 필터링 한다.</p>
</li>
</ol>
<h2 id="csrf">CSRF</h2>
<p>CSRF는 사용자의 권한을 악용하여 공격자가 원하지 않는 요청을 보내는 것이다. 구체적으로 <strong>로그인된 사용자를 공격자가 의도한 행위</strong> 특정 웹사이트에 요청하게 한다.</p>
<h4 id="csrf-3가지-조건">CSRF 3가지 조건</h4>
<blockquote>
<p>CSRF가 성공하려면, 아래 <strong>3가지 조건</strong>이 만족되어야 한다.</p>
</blockquote>
<ol>
<li>사용자는 보안이 취약한 서버로부터 <strong>이미 로그인되어 있는 상태</strong>여야 한다.</li>
<li><strong><ins>쿠키 기반의 서버 세션 정보를 획득</strong>할 수 있어야 한다.</li>
<li>공격자는 <strong>서버를 공격하기 위한 요청 방법에 대해 미리 파악</strong>하고 있어야 한다. (사용자 패스워드 변경은 어떤 URI와 메소드를 사용하는지)</li>
</ol>
<h4 id="csrf-절차">CSRF 절차</h4>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/a5af929e-fb7f-4aaa-b1f5-0114ceff0184/image.png" alt=""></p>
<ul>
<li><p>사용자는 웹 사이트에 로그인을 한다.</p>
</li>
<li><p>로그인이 성공하면 (로그인 관리 방식이 세션이라는 가정하에) <strong><ins>사용자의 브라우저에 쿠키 정보가 저장</strong>된다.</p>
</li>
<li><p>공격자의 피싱 메일 혹은 악성 스크립트가 작성된 페이지 링크를 열람 한다면, 악성 페이지에 이동하게 된다.</p>
</li>
<li><p>악성 사이트에서 특정 웹 사이트에 <strong>사용자의 쿠키 정보를 가지고 위조된 요청 정보를 전송</strong>한다.</p>
</li>
<li><p>이로 인해 사용자는 패스워드 변경 등과 같은 의도하지 않은 행동을 수행하게 된다.</p>
</li>
</ul>
<h4 id="csrf-방지">CSRF 방지</h4>
<p><strong><ins>가장 단순한 방법은 JWT 토큰을 사용</strong>하는 것이다. 로그인을 성공하면 사용자에게 JWT 토큰을 발급하고, 해당 토큰을 쿠키가 아닌, 로컬 스토리지에 저장하면 CSRF를 방지할 수 있다. 하지만 <strong>XSS 공격에 토큰이 탈취</strong>당할 수 있다. 따라서 토큰을 발급할 때 <strong>ACCESS, REFRESH 두 중류의 토큰</strong>을 발급한다. </p>
<h3 id="🗒️ref">🗒️REF</h3>
<p><a href="https://devscb.tistory.com/123">https://devscb.tistory.com/123</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 compose]]></title>
            <link>https://velog.io/@jae-jang/%EB%8F%84%EC%BB%A4-compose</link>
            <guid>https://velog.io/@jae-jang/%EB%8F%84%EC%BB%A4-compose</guid>
            <pubDate>Tue, 28 Jan 2025 12:29:00 GMT</pubDate>
            <description><![CDATA[<p>사실 도커의 모든 CLI 명령어는 <code>compose</code> 로 바꿀 수 있다. 개인적으로 도커를 사용할 때는 항상 <code>compose</code> 를 사용할 것 같다. 그만큼 굉장히 편리한 기능이다. 왜냐하면 <code>compose</code> 는 여러개의 컨테이너를 하나로 묶어서 관리할 수 있는 서비스를 제공한다. 또한 복잡한 명령어를 단순화 시켜주는 강력한 기능을 제공한다.</p>
<p>아래는 mysql 이미지를 이용해서 컨테이너를 띄우는 명령어이다. 도커 허브의 공식 문서를 참조했다.</p>
<pre><code class="language-shell">docker run -e MYSQL_ROOT_PASSWORD=password1234 -p 3306:3306 {host}:/var/lib/mysql -d mysql</code></pre>
<ul>
<li>위의 명령어를 계속 사용하다 보면 <code>Mysql</code> 컨테이너를 띄우기가 매~우 귀찮아진다. 처음에는 괜찮을 지도 모르겠지만, 컨테이너를 내리고 올리기를 반복하면 저렇게 긴 명령어를 계속 치다보면.. 상당히 힘들다.</li>
</ul>
<p>그러니까 <code>compose</code> 기능을 사용하자. 위의 명령어와 동일한 기능을 하게끔 <code>compose.yml</code> 파일을 아래와 같이 작성해준다.</p>
<pre><code class="language-yml">services:
  my-db:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: pwd1234
      MYSQL_DATABASE: mydb # MySQL 최초 실행 시 mydb라는 데이터베이스를 생성해준다.
    volumes:
      - ./mysql_data:/var/lib/mysql
    ports:
      - 3306:3306</code></pre>
<ul>
<li>파일 작성후 <code>docker compose up -d</code> 를 작성하면 끝이다. 이는 위의 긴 명령어와 완전히 동일한 기능을 한다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커]]></title>
            <link>https://velog.io/@jae-jang/%EB%8F%84%EC%BB%A4</link>
            <guid>https://velog.io/@jae-jang/%EB%8F%84%EC%BB%A4</guid>
            <pubDate>Tue, 28 Jan 2025 12:18:41 GMT</pubDate>
            <description><![CDATA[<h2 id="개요-도커를-왜-배워야-할까">개요: 도커를 왜 배워야 할까?</h2>
<img src="https://velog.velcdn.com/images/jae-jang/post/9886a5e4-a2d5-426c-bce9-e62b2fb6def6/image.png" width="70%" height="n%">

<p>직접 깡으로 배포하는 경험이 있는 사람에게 도커의 편안함이 많이 와닿을 것이다. 나는 <code>SSH</code> 에서 <code>node</code> <code>db</code> 를 직접 설치하고, 각각의 환경변수 셋팅하는 과정에서 많은 에러가 발생했다. 또한 해결하는 과정도 역시 쉽지 않았다. 애초에 <strong>에러가 많이 발생하는 이유는 환경이 독립되지 않아서 충돌이 발생</strong>했기 때문이다. 하지만 도커를 사용하면 이러한 걱정을 할 필요가 없다. 컨테이너를 제공함으로써 독립된 환경을 제공하고, 설치 또한 명령어 한줄로 모든 게 다 된다. </p>
<h3 id="도커">도커</h3>
<p>도커를 이해하기 위해서는 컨테이너와 이미지라는 개념을 알고 있어야한다. 먼저 이 두용어에 대해서 알아보자. 컨테이너란 호스트 컴퓨터 내부의 독립된 환경의 미니 컴퓨터이다. </p>
<h4 id="1-컨테이너">1. 컨테이너</h4>
<img src="https://velog.velcdn.com/images/jae-jang/post/1bd80028-e14f-4578-9f2c-60fde31a119a/image.png" width="70%" height="n%">

<ul>
<li><p>하나의 호스트 컴퓨터에 여러개의 컨테이너 환경을 구성할 수 있다. </p>
</li>
<li><p>컨테이너 환경은 독립적이기 때문에, 각각 독자적인 저장 공간과 네트워크(ip. port) 를 가지고 있다.</p>
</li>
</ul>
<h4 id="2-이미지">2. 이미지</h4>
<img src="https://velog.velcdn.com/images/jae-jang/post/9c028b4a-cb88-4bbf-babf-254b249d57ec/image.png" width="50%" height="60%">

<ul>
<li><p>이미지는 닌텐도 칩에 비유할 수 있겠다. 닌텐도 칩은 각각 게임을 탑재하고 있다. 예시로 마리오 칩이 있다면 그것을 닌텐도에 꼽기만 하면 닌텐도에서 바로 마리오 게임을 즐길 수 있게 된다. 도커에서 이미지는 이와 매우 유사하다. 예시로 <code>Mysql</code> 이라는 이미지를 컨테이너 (미니컴퓨터) 에 꼽기만 하면 해당 컨테이너에서 <code>Mysql</code> 을 사용할 수 있다.</p>
</li>
<li><p>즉, 도커는 이미지를 통해서 뛰어난 이식성을 갖추고 있다.</p>
</li>
</ul>
<blockquote>
<p>따라서 도커에서 컨테이너를 띄울 때 보통 이미지와 함께 컨테이너를 띄운다. 이미지를 사용하지 않으면 사실상 도커를 사용하는 의미가 없기 때문이다.</p>
</blockquote>
<hr>
<h3 id="도커의-흐름">도커의 흐름</h3>
<p>도커의 전반적인 흐름은 다음과 같다.</p>
<blockquote>
<ol>
<li>이미지를 다운 받는다. </li>
<li>다운로드 받은 이미지를 컨테이너에 올린다. 
2-1. 만약에 서버를 컨테이너에 올린 경우 포트 매핑을 신경 써준다. </li>
</ol>
</blockquote>
<p>간단하게 <code>nginx</code> 서버를 도커를 이용해서 띄어보자. </p>
<p>그러기 위해서는 우선 <code>nginx</code> 이미지 부터 <code>dockerhub</code> 로 부터 다운로드 받을 수 있도록 하자.</p>
<pre><code class="language-bash">docker pull nginx</code></pre>
<p>다음으로는 image 가 잘 다운로드 받아졌는지 확인하기 위해서 아래 명령어를 수행하자.</p>
<pre><code class="language-bash">docker image ls</code></pre>
<p>마지막 과정으로 <code>nginx</code> 이미지를 컨테이너에 올려 띄우면 끝이다.</p>
<pre><code class="language-bash">docker run --name my-server -d -p 80:80 nginx</code></pre>
<ul>
<li><code>run</code> 은 컨테이너를 띄우는 명령어인데 컨테이너의 이름을 <code>my-server</code> 로 하였다. </li>
<li><code>-d</code> 는 백그라운드 환경에서 컨테이너를 띄우는 뜻이고, <code>-p 80:80</code> 는 호스트 포트 80번과 컨테이너 포트 80번을 이어준 것이다. 아래의 그림을 참고하도록 하자.
<img src="https://velog.velcdn.com/images/jae-jang/post/1366f43b-6efb-4b85-9457-1bf6203178d9/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 윈도우 볼륨 문제]]></title>
            <link>https://velog.io/@jae-jang/%EB%8F%84%EC%BB%A4-%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%B3%BC%EB%A5%A8-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@jae-jang/%EB%8F%84%EC%BB%A4-%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%B3%BC%EB%A5%A8-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 23 Jan 2025 12:54:58 GMT</pubDate>
            <description><![CDATA[<p>mysql 사용하는 컨테이너를 만들고, 볼륨을 만들때 아래의 명령어를 수행한다.</p>
<pre><code class="language-bash">docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -v {호스트의 절대경로}:/var/lib/mysql -d mysql</code></pre>
<p>그런데 윈도우의 경우 리눅스와 경로를 찾는 방법이 달라서 호스트 컴퓨터에서 텅빈 디렉터리가 생성된다. 경로를 제대로 지정해주기 위해서는 아래처럼 <code>&quot;&quot;</code> 를 붙여주면 된다.</p>
<pre><code class="language-bash">docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -v &quot;{호스트의 절대경로}:/var/lib/mysql&quot; -d mysql</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 메모리 구성 With Static]]></title>
            <link>https://velog.io/@jae-jang/%EC%9E%90%EB%B0%94-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@jae-jang/%EC%9E%90%EB%B0%94-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Tue, 07 Jan 2025 05:20:04 GMT</pubDate>
            <description><![CDATA[<h2 id="자바-메모리의-구성">자바 메모리의 구성</h2>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/a138b6a4-3148-4748-ac4b-f3ece54f12f0/image.png" alt="">
자바의 메모리 구조는 크게 <strong>메서드 영역, 스택 영역, 힙 영역</strong> 3개로 나눌 수 있다.</p>
<ul>
<li><p>메서드 영역: 프로그램을 실행하는데 필요한 <strong>공통 데이터를 관리</strong>한다. 이러한 데이터에는 <strong>클래스, static, 상수</strong>등이 있으며, 메서드 영역은 모든 영역에서 공유한다.</p>
<blockquote>
<p>클래스 변수 = 정적 변수 = static 변수</p>
</blockquote>
</li>
<li><p>스택 영역: 자바 실행 시, 하나의 실행 스택이 생성된다. 각 스택 프레임은 <strong>지역 변수, 중간 연산 결과, 메서드 호출 정보</strong>등을 포함한다.</p>
</li>
<li><p>힙 영역: <strong>인스턴스와 배열</strong>이 생성되는 영역이다. 가비지 컬렉션이 이루어지는 영역이며, 더 이상 참조되지 않는 객체는 GC에 의해 제거된다.</p>
</li>
</ul>
<h2 id="스택-영역">스택 영역</h2>
<p>스택 영역은 스택이라는 자료구조를 떠올리면 이해하기가 쉽다. 스택은 가장 나중에 들어간 데이터가 가장 빨리 나오는 LIFO 구조로 아래 코드를 통해 쉽게 이해할 수 있다.</p>
<pre><code class="language-java">public class JavaMemoryMain1 {
    public static void main(String[] args) {
        System.out.println(&quot;main start&quot;);
        method1();
        System.out.println(&quot;main end&quot;);
    }

    static void method1() {
        System.out.println(&quot;method1 start&quot;);
        method2();
        System.out.println(&quot;method1 end&quot;);
    }

    static void method2() {
        System.out.println(&quot;method2 start&quot;);
        System.out.println(&quot;method2 end&quot;);
    }
}</code></pre>
<blockquote>
<p>실행결과는 아래와 같다. <code>main -&gt; method1 -&gt; method2</code> 순서대로 호출을 하였지만 <code>method2 -&gt; method1 -&gt; main</code> 순서대로 코드가 종료된다. 이를 통해 스택 영역은 LIFO의 형태로 동작됨을 확인할 수 있다.</p>
</blockquote>
<pre><code>main start
method1 start
method2 start
method2 end
method1 end
main end</code></pre><h2 id="메소드-영역과-힙영역">메소드 영역과 힙영역</h2>
<p>메소드 영역은 클래스나 <code>static</code> 등 공통으로 사용하는 정보가 저장되는 영역이고, 힙영역은 주로 인스턴스나 배열등 <code>new</code> 키워드를 써서 만들어진 정보가 저장되는 영역이다.</p>
<p>왜 이러한 영역들을 구분해놨을까? 클래스는 붕어빵 틀과 같은 설계도인데 이 설계도가 여러개 있을 필요가 전혀 없다. <del>붕어빵 장사할 때 붕어빵 기계 10개 있을 필요가 있을까? 장사 망한다</del> 이러한 이유로 공통적으로 쓰이는 영역인 메소드 영역과 개별 객체 혹은 배열이 저장되는 힙영역으로 나눔으로써 메모리 최적화를 수행할 수 있다.</p>
<h2 id="static">static</h2>
<p>사실 이번 장의 하이라이트는 <code>static</code> 이 친구이다. 크게 <code>static</code> 변수와 <code>static</code> 메서드로 구분된다. 본격적으로 시작하기 전에 아래의 질문을 던져보자</p>
<h3 id="static-왜-필요한데">static 왜 필요한데?</h3>
<p>크게 정적 변수와 정적 메서드로 나뉘는데 이들은 <strong>클래스에서 공통으로 사용할 변수나 유틸 메서드가 필요</strong>할 때 사용한다. 먼저 <code>static</code> 변수부터 어떻게 활용되는지 살펴보자. 내가 만약에 자동차 판매 횟수를 카운팅 해보고 싶다고 해보자. 이때 아래와 같이 코드를 짰다.</p>
<pre><code class="language-java">public Car {
    public int count;
    public String name;
    public Car(String name){
        this.name = name;
        count++;
    }
}

public static void main(String[] args){
    Car car1 = new Car(&quot;포르쉐&quot;)
    Car car2 = new Car(&quot;G80&quot;)
    Car car3 = new Car(&quot;테슬라&quot;)
    sout(&quot;count=&quot; + car3.count) //잉? 왜 1인데?
}</code></pre>
<ul>
<li>위의 코드는 아래와 같이 힙 영역이 생성된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jae-jang/post/4e5f8ab0-90f1-47c2-aded-10e7962115d4/image.png" alt=""></p>
<ul>
<li><p>단순히 각 인스턴스 변수인 <code>count</code> 값이 올라가니, 당연히 카운트가 안될 수 밖에 없다.</p>
</li>
<li><p>그럼 어떻게 해결할까? <code>static</code> 변수 쓰면 된다.</p>
<pre><code class="language-java">public Car {
  public static int count;
  public String name;
  public Car(String name){
      this.name = name;
      count++;
  }
}
</code></pre>
</li>
</ul>
<p>public static void main(String[] args){
    Car car1 = new Car(&quot;포르쉐&quot;)
    Car car2 = new Car(&quot;G80&quot;)
    Car car3 = new Car(&quot;테슬라&quot;)
    sout(&quot;count=&quot; + car3.count) //이제는 3이다.
}</p>
<pre><code>![](https://velog.velcdn.com/images/jae-jang/post/c783adfb-6689-48c1-b32c-d1fd9b24000a/image.png)
- 이렇게 static 키워드만 넣어주면 `car1` `car2` `car3` 모두  메서드 영역의 `count` 변수의 값을 증가시킨다.

---

다음으로 `static` 메서드는 언제 사용하는지 살펴보자. 이는 수학 합산 계산처럼 간단한 `util` 메서드로써 활용된다. 클래스를 만들었는데 인스턴스 변수는 필요 없고 단순히 메서드 기능만 제공할 때 사용된다.

`static` 메서드는 사용할 때 주의사항이 필요하다. 바로 `static` 메서드는 다른 `static` 메서드나 `static` 변수에는 접근할 수 있지만, 인스턴스 메서드와 변수에는 접근할 수 없다는 것이다. 그런데 이것은 너무나도 당연한 얘기이다. 힙영역에 인스턴스가 생성됐는지도 모르고, 어떠한 인스턴스를 가르키는지도 모르는데 어떻게 `static` 메서드가 인스턴스를 접근할 수 있을까..? 당연히 못한다. 

```java
public class DecoData {

    private int instanceValue;
    private static int staticValue;

    public static void staticCall() {
        //instanceValue;  //인스턴스 변수 접근, compile error
        //instanceMethod; //인스턴스 메서드 접근, compile error

        staticValue++;
        staticMethod();
    }

    //인스턴스 메서드는 정적, 인스턴스 변수에 모두 접근 가능
    public void instanceCall() {
        instanceValue++;
        instanceMethod();

        staticValue++;
        staticMethod();
    }

    private void instanceMethod() {
        System.out.println(&quot;instanceValue=&quot; + instanceValue);
    }

    private static void staticMethod() {
        System.out.println(&quot;staticValue=&quot; + staticValue);
    }
}
</code></pre><ul>
<li><p><code>staticCall</code> 인 정적 메서드는 인스턴스 변수와 메서드에 접근 못한다.</p>
</li>
<li><p>반면 <code>instanceCall</code> 인 인스턴스 메서드는 인스턴스 변수와 정적 변수 모두 접근 가능하다. </p>
</li>
</ul>
<hr>
<h2 id="부록">부록</h2>
<h4 id="클래스-메서드-혹은-변수-호출시">클래스 메서드 혹은 변수 호출시..</h4>
<p>참고로 클래스 변수를 호출할 때 아래와 같이 2가지 방식이 있다.</p>
<pre><code>Car car1 = new Car(&quot;포르쉐&quot;)
car1.count #방법1
Car.count  #방법2</code></pre><ul>
<li>방법 2를 써야 한다. 왜냐하면 접근하는 count 변수가 클래스 변수임을 명확하게 파악할 수 있기 때문이다. 방법1은 인스턴스 변수로 오인할 여지가 있다</li>
</ul>
<h4 id="메인-함수도-static">메인 함수도 static?</h4>
<p>이제 <code>static</code> 에 대해서 이해했으므로 드디어 <code>main</code> 함수에 대해서 이해할 수 있다. <code>main</code> 함수는 프로그램 호출시 가장 먼저 시작되는 곳으로 메서드 영역에 올라온다.(<code>static</code> 이므로)</p>
<pre><code class="language-java">public class JavaMemoryMain1 {
    public static void main(String[] args) {
        method1();
    }

    static void method1() {
        System.out.println(&quot;method1 start&quot;);
    }
}</code></pre>
<ul>
<li><p><code>main</code> 문 밖의 함수를 호출하기 위해서 항상 <code>static</code> 을 붙여준 것이 이제 보일까? </p>
</li>
<li><p><code>main</code>은 정적 메서드이니까 다른 정적 메서드만을 참조할 수 있기 때문이다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 접근 제어자]]></title>
            <link>https://velog.io/@jae-jang/%EC%9E%90%EB%B0%94-%EC%A0%91%EA%B7%BC-%EC%A0%9C%EC%96%B4%EC%9E%90</link>
            <guid>https://velog.io/@jae-jang/%EC%9E%90%EB%B0%94-%EC%A0%91%EA%B7%BC-%EC%A0%9C%EC%96%B4%EC%9E%90</guid>
            <pubDate>Tue, 07 Jan 2025 04:59:31 GMT</pubDate>
            <description><![CDATA[<h4 id="접근-제어자-종류">접근 제어자 종류</h4>
<p>자바는 4가지 종류의 접근 제어자를 제공한다.</p>
<ul>
<li><code>private</code>: 모든 외부 호출을 막는다.</li>
<li><code>default</code>: 같은 패키지 안에서 호출은 허용한다.</li>
<li><code>protected</code>: 같은 패키지 안에서 호출은 허용한다. 패키지가 달라도 상속 관계 호출은 허용한다.</li>
<li><code>public</code>: 모든 외부 호출을 허용한다.</li>
</ul>
<h4 id="접근-제어자와-캡슐화">접근 제어자와 캡슐화</h4>
<p>캡슐화는 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어서 외부에서의 접근을 제한하는 것을 말한다. 이때 접근 제어자를 활용하면 캡슐화를 더욱 견고히 할 수 있다. 이에 대한 방법은 크게 2가지이다. </p>
<ul>
<li><p><strong>첫번째는 클래스의 데이터를 숨기는 것이다.</strong> 객체 내부의 데이터를 외부에서 함부로 접근하게 두면, 클래스 안에서 데이터를 다루는 모든 로직을 무시하고 데이터가 변경 될 수 있다. 즉, 캡슐화가 깨진다. </p>
</li>
<li><p><strong>두번째는 외부에서 사용하지 않는 기능을 숨기는 것이다.</strong> 우리가 자동차를 운전할 때 복잡한 엔진 조절 기능, 배기 기능을 알 필요가 없다. 단지 운전에 필요한 엑셀과 핸들 정도의 기능만 알면 된다. </p>
<pre><code class="language-java">public class BankAccount {
  private int balance;

  public BankAccount() {  
      balance = 0;
  }

  public void deposit(int amount) {
      if (isAmountValid(amount)) {
          balance += amount;
      } else {
          System.out.println(&quot;유효하지 않은 금액입니다.&quot;);
      }
  }

  public void withdraw(int amount) {
      if (isAmountValid(amount)) {
          balance -= amount;
      } else {
          System.out.println(&quot;유효하지 않은 금액이거나 잔액이 부족합니다.&quot;);
      }
  }

  public int getBalance() {
      return balance;
  }

  private boolean isAmountValid(int amount) {
      return amount &gt; 0;
  }
}</code></pre>
</li>
<li><p>위의 코드는 캡슐화가 잘 된 코드이다. 왜냐하면 데이터는 모두 숨기고, 꼭 필요한 기능만 외부로 노출하였기 때문이다.</p>
</li>
<li><p><code>isAmountValid</code> 는 내부에서만 필요한 기능이므로 외부로 노출시킬 필요가 없다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 프로그래밍]]></title>
            <link>https://velog.io/@jae-jang/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@jae-jang/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Mon, 06 Jan 2025 04:47:47 GMT</pubDate>
            <description><![CDATA[<h3 id="절차지향-프로그래밍-vs-객체지향-프로그래밍">절차지향 프로그래밍 vs 객체지향 프로그래밍</h3>
<p>이 둘의 차이점을 명확하게 구분한다면, 객체지향에 대한 이해가 더욱 쉬워진다. 먼저 절차 지향은 말 그대로 절차를 지향한다. 즉, 프로그램의 흐름을 순차적으로 따르며 처리하는 방식이다. 반면에 객체 지향은 말 그대로 객체를 지향한다. 이는 실제 세계의 사물이나 사건을 객체로 보고, 이러한 객체들 간의 상호작용을 중심으로 프로그래밍 하는 방식이다.</p>
<blockquote>
<p>절차 지향은 데이터와 해당 데이터에 대한 처리 방식이 분리되어 있지만, 객체 지향에서는 데이터와 그 데이터에 대한 행동(메서드)이 하나의 객체 안에 포함되어 있다.</p>
</blockquote>
<h3 id="절차지향-프로그래밍">절차지향 프로그래밍</h3>
<pre><code class="language-java">//메인에서 활용할 클래스
public class MusicPlayerData {
    int volume = 0;
    boolean isOn = false;
}</code></pre>
<pre><code class="language-java">//메인
public class MusicPlayerMain3 {
    public static void main(String[] args) {
        MusicPlayerData data = new MusicPlayerData();
        on(data);
        volumeUp(data);
        volumeUp(data);
        volumeDown(data);
        showStatus(data);
        off(data);
    }

    private static void showStatus(MusicPlayerData data) {
        System.out.println(&quot;음악 플레이어 상태 확인&quot;);
        if (data.isOn) {
            System.out.println(&quot;음악 플레이어 온 볼륨:&quot; + data.volume);
        } else {
            System.out.println(&quot;음악 플레이어 off&quot;);
        }
    }

    private static void volumeDown(MusicPlayerData data) {
        data.volume--;
        System.out.println(&quot;음악 플레이어 볼륨:&quot; + data.volume);
    }

    private static void volumeUp(MusicPlayerData data) {
        data.volume++;
        System.out.println(&quot;음악 플레이어 볼륨:&quot; + data.volume);
    }

    private static void on(MusicPlayerData data) {
        data.isOn = true;
        System.out.println(&quot;음악 플레이어를 시작합니다.&quot;);
    }

    private static void off(MusicPlayerData data) {
        data.isOn = false;
        System.out.println(&quot;음악 플레이어를 종료합니다.&quot;);
    }

}</code></pre>
<ul>
<li>여러 기능을 메서드로 만들어서 기능이 모듈화가 되었다. 이를 통해 중복 로직 제거가 가능하고, 기능을 수정할 때 메서드 내부만 변경하면 되며, 메서드 이름을 추가함으로써 코드를 더욱 쉽게 이해할 수 있도록 만들었다.</li>
<li><strong>절차지향은 사용할 데이터와 기능이 따로 분리되어 있는 한계가 있다.</strong> 위의 코드에서 데이터는 <code>MusicPlayerData</code> 객체에 존재하고, 기능은 <code>MusicPlayerMain3</code> 에 존재한다. 따라서 이후에 <code>MusicPlayerData</code> 데이터가 변경되면 <code>MusicPlayerMain3</code> 의 메서드도 변경 해야 한다. 즉, 유지 보수 관점에서 관리 포인트가 2곳으로 늘어난다.</li>
</ul>
<h3 id="객체-지향-프로그래밍">객체 지향 프로그래밍</h3>
<p>위의 절차 지향 프로그래밍 코드를 객체 지향으로 바꾸면 아래와 같다.</p>
<pre><code class="language-java">public class MusicPlayer {

    int volume = 0;
    boolean isOn = false;

    void on() {
        isOn = true;
        System.out.println(&quot;음악 플레이어를 시작합니다.&quot;);
    }

    void off() {
        isOn = false;
        System.out.println(&quot;음악 플레이어를 종료합니다.&quot;);
    }

    void volumeUp() {
        volume++;
        System.out.println(&quot;음악 플레이어 볼륨:&quot; + volume);
    }

    void volumeDown() {
        volume--;
        System.out.println(&quot;음악 플레이어 볼륨:&quot; + volume);
    }

    void showStatus() {
        System.out.println(&quot;음악 플레이어 상태 확인&quot;);
        if (isOn) {
            System.out.println(&quot;음악 플레이어 온 볼륨:&quot; + volume);
        } else {
            System.out.println(&quot;음악 플레이어 off&quot;);
        }
    }
}</code></pre>
<pre><code class="language-java">public class MusicPlayerMain4 {
    public static void main(String[] args) {
        MusicPlayer player = new MusicPlayer();
        player.on();
        player.volumeUp();
        player.volumeUp();
        player.volumeDown();
        player.showStatus();
        player.off();
    }
}</code></pre>
<ul>
<li>클래스 내부에 데이터와 관련된 기능(메서드)가 함께 포함되어 있다. 따라서 <code>MusicPlayer</code> 객체를 생성하고, 필요한 기능(메서드)를 호출하기만 하면 된다. <code>MusicPlayer</code> 를 사용하는 입장에서는 이제 <code>MusicPlayer</code> 내부의 어떤 데이터가 있는지 전혀 몰라도 된다. 그냥 필요한 기능을 호출해서 사용하기만 하면 된다.</li>
<li>따라서 <code>MusicPlayer</code> 내부 코드가 바뀐 경우에 다른 코드를 변경하지 않아도 된다.</li>
</ul>
<blockquote>
<p><code>MusicPlayer</code> 를 보면 음악 플레이어를 구상하기 위한 속성과 기능이 마치 하나의 캡슐에 쌓여있는 것 같다. 이처럼 속성과 기능을 하나로 묶어서 필요한 기능을 메서드를 통해 외부에 제공하는 것을 캡슐화라고 한다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>