Java (18)

 

 

문제

Merge Sort를 이용하여 int 형 배열을 정렬하시오.

조건

  • Arrays.sort 사용 금지
  • 병합 정렬을 이용하여 정렬

 

병합 정렬이란??

우선 입력받은 배열을 길이가 1인 배열이 될 때까지 계속 반으로 나눈다.

나눈 배열을 2개씩 병합하여 정렬한다.

길이가 2인 배열 병합 -> 길이가 4인 배열 병합 -> 길이가 8인 배열 병합 ...

병합할 때마다 요소들을 정렬한다.

 

출처 : GeeksforGeeks merge Algorithm

 

 

해결 방법

'반으로 나눈다 -> 병합한다' 라는 반복과정이 있기 때문에 반복, 재귀 중에 한가지로 해결할 수 있습니다.

저는 재귀를 활용하여 풀었습니다.

배열을 중간을 기준으로 나눠야 하기 때문에 length / 2 를 기준으로 배열을 left, right 두부분으로 분리하였습니다.

배열의 길이가 1이 될 때까지 이과정을 반복해야 하기 때문에 left, right 배열을 초기화하는 과정에서 바로 재귀를 호출하였습니다.

브레이크 포인트로 길이가 1이하가 될 경우 멈추도록 했습니다.

이제 쪼개진 배열을 오름차순으로 병합하면 됩니다.

이때 left 배열과 right 배열은 각각 이미 정렬이 된 상태이기 때문에 두 배열을 앞에서부터 비교하면서 더 작은 쪽의 요소를 넣어주고 인덱스 카운트를 올려줍니다.

그럼 각각의 인덱스를 기억해야하기 때문에 각각 인덱스를 담을 leftIdx, rightIdx를 선언합니다.

그리고 반복문으로 정렬할 배열의 길이만큼 반복해주면서 두 배열 중 작은 요소를 결과 배열에 넣고 해당 배열의 인덱스를 +1 시켜줍니다.

여기서 배열의 길이를 넘어버리면 안되기 때문에 배열의 길이를 넘을 경우 반대쪽 배열의 남은 요소를 모두 붙여넣고 반복을 종료시키도록 합니다.

 

 

코드

 

import java.util.Arrays;

public class MergeSort {
    public int[] mergeSort(int[] arr) {
        if (arr.length < 2) {
            return arr;
        }
        int middle = arr.length / 2;
        int[] left = mergeSort(Arrays.copyOfRange(arr, 0, middle));
        int[] right = mergeSort(Arrays.copyOfRange(arr, middle, arr.length));

        arr = new int[left.length + right.length];
        int leftIdx = 0;
        int rightIdx = 0;
        for (int i = 0; i < arr.length; i++) {
            if (leftIdx >= left.length) {
                System.arraycopy(right, rightIdx, arr, i, right.length - rightIdx);
                break;
            } else if (rightIdx >= right.length) {
                System.arraycopy(left, leftIdx, arr, i, left.length - leftIdx);
                break;
            } else if(left[leftIdx] > right[rightIdx]) {
                arr[i] = right[rightIdx];
                rightIdx++;
            } else {
                arr[i] = left[leftIdx];
                leftIdx++;
            }
        }
        return arr;
    }
}

 

 

 

깃허브 코드 보러가기

 

감사합니다. 좋은 하루 되세요. 🤗

 

 

 

 

 

문제

 

문제 링크

카카오 성격 유형 검사지를 만들려고 합니다.

성격 유형은 아래와 같습니다.

지표 번호 성격 유형
1번 지표 라이언형(R), 튜브형(T)
2번 지표 콘형(C), 프로도형(F)
3번 지표 제이지형(J), 무지형(M)
4번 지표 어피치형(A), 네오형(N)

각각의 지표에 대해서 점수가 높은 알파벳을 조합하여 성격 유형 결과가 나옵니다. -> mbti를 생각하면 됨

ex) RCMA

 

검사지에는 총 n개의 질문이 있고 선택지 총 7개 입니다.

  • 매우 비동의
  • 비동의
  • 약간 비동의
  • 모르겠음
  • 약간 동의
  • 동의
  • 매우 동의

 

검사자의 선택에 따라(동의/비동의) 한가지 지표의 한 개의 성격 유형에 대한 점수를 얻습니다. (1번 지표일 경우 비동의가 R, 동의가 T일 수도 있고, 동의가 R, 비동의가 T일 수 있음)

R(라이언형)이 비동의, T(튜브형)이 동의일 경우 점수는 아래와 같습니다.

답변 점수
매우 비동의 라이언형 3점
비동의 라이언형 2점
약간 비동의 라이언형 1점
모르겠음 어떤 성격 유형도 점수를 얻지 않음
약간 동의 튜브형 1점
동의 튜브형 2점
매우 동의 튜브형 3점

 

점수가 높은 지표를 조합하여 성격유형 검사 결과를 출력합니다.

 

입력

  • survey : survey[i] 번째 문자열은 i+1 번째 문제의 성격유형을 의미 (2글자로 1번째 문자는 비동의, 2번째 문자는 동의에 해당하는 성격유형)
    • ex) "RT", "MJ"
  • choices : choices[i]는 검사자가 선택한 i+1번째 질문의 선택지

 

 

해결 과정

 

처음에는 Map을 이용하여 풀려고 했으나 Map으로 푸는 건 너무 흔할 것 같아 새로운 시도를 해보고 싶어 배열로 풀었습니다.

 

 

코드

 

public String solution(String[] survey, int[] choices) {
    String answer = "";
    // 설문자의 점수를 담을 정수형 배열 선언, 차례대로 R, C, J, A 형의 점수, 반대 점수는 음수로 더한다.
    int[] scores = new int[4];

    // 설문지 점수표를 담을 정수형 배열 선언, i번째 인덱스의 숫자가 i점의 점수
    int[] scoreTable = {0, 3, 2, 1, 0, 1, 2, 3};

    // 계산할 점수 지표를 담을 캐릭터형 선언
    char indicator;

    // 질문지의 길이만큼 순회하여 choices의 i번째 요소를 꺼낸다.
    for(int i = 0; i < choices.length; i++) {
        int choice = choices[i];
        // 꺼낸 점수가 4일 경우 continue
        if (choice == 4) continue;
        // 꺼낸 점수가 1~3일 경우
        if (choice < 4) {
            // survey[i]의 0번째 캐릭터형이 점수 지표
            indicator = survey[i].charAt(0);
        } else {  // 꺼낸 점수가 5~7일 경우
            // survey[i]의 1번째 캐릭터형이 점수 지표
            indicator = survey[i].charAt(1);
        }
        // indicator 에 따라 점수를 계산
        switch(indicator) {
            case 'R':
                scores[0] += scoreTable[choice];
                break;
            case 'T':
                scores[0] -= scoreTable[choice];
                break;
            case 'C':
                scores[1] += scoreTable[choice];
                break;
            case 'F':
                scores[1] -= scoreTable[choice];
                break;
            case 'J':
                scores[2] += scoreTable[choice];
                break;
            case 'M':
                scores[2] -= scoreTable[choice];
                break;
            case 'A':
                scores[3] += scoreTable[choice];
                break;
            case 'N':
                scores[3] -= scoreTable[choice];
                break;
            default: break;
        }
    }
    // 점수 배열의 0번째 인덱스가 0이상인 경우 결과 문자열에 R, 음수일경우 T를 붙인다.
    if (scores[0] >= 0) {
        answer += "R";
    } else answer += "T";
    // 점수 배열의 1번째 인덱스가 0이상인 경우 결과 문자열에 C, 음수일경우 F를 붙인다.
    if (scores[1] >= 0) {
        answer += "C";
    } else answer += "F";
    // 점수 배열의 2번째 인덱스가 0이상인 경우 결과 문자열에 J, 음수일 경우 M을 붙인다.
    if (scores[2] >= 0) {
        answer += "J";
    } else answer += "M";
    // 점수 배열의 3번째 인덱스가 0이상인 경우 결과 문자열에 A, 음수일 경우 N을 붙인다.
    if (scores[3] >= 0) {
        answer += "A";
    } else answer += "N";
    return answer;
}

 

 

 

깃허브 코드 보러가기

 

 

 

 

 

문제

정수를 요소로 갖는 배열을 입력받아 오름차순으로 정렬하여 리턴합니다.

조건

  • sort 메서드 사용 금지
  • 퀵 정렬을 구현
  • 오름차순으로 정렬

 

 

해결과정

퀵 정렬을 정확하게 모르기 때문에 구현할 수가 없어 퀵 정렬 방법만 검색하여 참고한 후 코드는 직접 구현하였습니다.

퀵 정렬은 pivot을 기준으로 작은 수들, 큰 수들을 각각 새로운 배열에 넣는 과정을 반복하여 정렬하는 방식입니다.

시간복잡도는 O(nlog n) 에서 최악의 경우 O(n^2) 입니다.

검색한 퀵 정렬의 개념을 토대로 재귀를 이용하여 간단하게 로직을 구현했습니다.

pivot은 주로 맨 끝 요소를 기준으로 하는 것으로 보여서 입력받은 맨끝요소를 pivot으로 하고

배열의 나머지 요소를 순회하면서 작은 수와 큰 수를 각각 나눠 리스트에 집어넣습니다.

(배열이 아닌 리스트를 사용한 이유는?

배열은 사이즈를 알고 있어야 생성할 수 있는데 pivot을 기준으로 큰 수가 몇 개이고 작은 수가 몇 개인지 순회하기 전까진 알 수 없어

번거롭더라도 사이즈가 가변하는 리스트에 넣은 후 순회를 마치면 toArray로 배열로 변환해주었습니다.)

그 후에 작은 수 배열을 재귀로 집어넣고 큰 수 배열도 마찬가지로 재귀에 집어넣습니다.

재귀 브레이크 포인트로 입력받은 배열의 길이가 1이하일 경우는 그대로 리턴하고 2일 경우는 정렬 후 리턴하도록 했습니다.

마지막으로 배열을 작은 수 배열 + pivot + 큰 수 배열 순으로 붙여서 결과 배열을 만든 후 리턴하였습니다.

 

 

코드

public int[] quickSort(int[] arr) {
    if (arr.length <= 1) {
        return arr;
    }
    if (arr.length == 2) {
        if (arr[0] > arr[1]) {
            int a = arr[0];
            arr[0] = arr[1];
            arr[1] = a;
        }
        return arr;
    }

    int[] answer = new int[arr.length];
    List<Integer> smallNums = new ArrayList<>();
    List<Integer> bigNums = new ArrayList<>();

    int pivot = arr[arr.length - 1];
    for (int i = 0; i < arr.length - 1; i++) {
        if (arr[i] < pivot) {
            smallNums.add(arr[i]);
        } else bigNums.add(arr[i]);
    }
    int[] smallArr = quickSort(smallNums.stream().mapToInt(Integer::intValue).toArray());
    int[] bigArr = quickSort(bigNums.stream().mapToInt(Integer::intValue).toArray());
    System.arraycopy(smallArr, 0, answer, 0, smallArr.length);
    answer[smallArr.length] = pivot;
    System.arraycopy(bigArr, 0, answer, smallArr.length + 1, bigArr.length);
    return answer;
}

 

 

 

깃허브 코드 보러가기

 

 

 

감사합니다. 좋은하루 되세요. 😄

 

 

자연수에서 소수(prime number)는 1보다 큰 수 중에 1과 자기자신만을 약수로 가지는 수이다.

따라서 자신보다 작은 수(1 제외)로 나눴을 때 항상 나누어 떨어지지 않아서 나머지가 존재한다는 의미다.

소수를 구하는 함수를 자바 언어를 사용하여 구현해보았다.

 

복잡하지 않게 작성하면 이렇게 구현할 수 있다.

public boolean prime (int num) {
	if(num == 0 || num == 1) return false;
	if(num == 2) return true;
    for(int i = 2; i < num; i++){
       if(num % i == 0) return false;
    }
    return true;
}

 

함수의 형식은 매개변수로 소수인지 구할 숫자를 입력받고 소수인지 여부를 true/false로 반환하는 모양으로 만들었다.

소수인지 구하기 전에 0과 1은 일단 소수가 아니므로 false를 리턴하고 2는 소수이므로 true를 리턴한다.

그리고 1보다 큰 수인 2부터 num보다 작은 num - 1까지 나누어떨어지는지 확인한다.

중간에 한번이라도 나누어떨어지면 소수가 아니므로 false를 리턴하고

num - 1 까지 확인을 마치면 true를 리턴한다.

 

여기서 더 효율적으로 코드를 짤 수는 없을까?

 

약수를 확인할 때 항상 2개씩 짝이 지어진다.

예를 들어 40의 약수를 구해보면

1, 2, 4, 5, 8, 10, 20, 40 이다.

이때 1과 40, 2와 20, 4와 10, 5와 8 이렇게 2개씩 짝이 지어진다.

그렇다면 맨 끝까지 일일이 나눠서 확인할 필요없이 중간 수 까지만 확인하면 끝까지(num - 1) 확인한 것과 같은 결과 일 것이다.

중간 수는 어떻게 구하는가?

2 x 20, 4 x 10, 5 x 8 = 40

중간 수는 자기자신을 곱했을 때 40이 나오는 수이다!

다시 말해 40의 제곱근이다.

자바에서 제곱근을 구하려면 Math.sqrt() 를 사용하면 된다.

다음은 제곱근까지만 확인하도록 구현해보았다.

 

    public boolean prime (int num) {
        if(num == 0 || num == 1) return false;
        if(num == 2) return true;
        if(num % 2 == 0) return false;
        for(int i = 3; i <= Math.sqrt(num); i++)
            if(num % i == 0) return false;
        return true;
    }

 

이 함수가 맨 처음에 만든 함수보다 반복문에서 num의 제곱근 까지만 반복하므로 더 효율적이다.

 

 

여기서 추가로 이웃이신 닉님의 도움으로 더 간단하게 작성했습니다!

짝수는 2로 나눠서 확인했기 때문에 3부터 +2하면서 확인하면 됩니다.

    public boolean prime (int num) {
        if(num == 0 || num == 1) return false;
        if(num == 2) return true;
        if(num % 2 == 0) return false;
        for(int i = 3; i <= Math.sqrt(num); i += 2)
            if(num % i == 0) return false;
        return true;
    }

 

 

 

 

읽어주셔서 감사합니다.

좋은 하루 되세요 ^^

+닉님 감사합니다😃

 

 

 

Tree구조를 발이 아닌 손으로 그린 거다.

 

 

 

❑ Tree

 

➤ 데이터 사이의 계층 관계를 나타내느 자료구조

➤ 이름 그대로 나무 모양이다.

➤ 비선형 구조

 

 

※ 용어 정리

  • node(노드) : 가지로 연결되어 있는 하나하나의 요소
  • edge(가지) : 노드 사이를 연결
  • root(루트) : 트리의 가장 윗부분에 위치한 노드
  • leaf(리프) : 가장 아랫부분에 위치한 노드, 아래에 노드가 더 이상 없는 노드
  • 안쪽노드 : 리프를 제외한 나머지 노드(루트 포함)
  • child node(자식 노드) : 가지로 연결된 아래쪽 노드, 노드는 자식을 여러 개 가질 수 있습니다
  • parent node(부모 노드) : 가지로 연결된 윗쪽 노드, 자식 노드는 하나의 부모 노드만 가질 수 있습니다.
  • sibling(형제) : 같은 부모를 가진 노드
  • ancestor(조상) : 어떤 노드의 위쪽으로 뻗어 나간 모든 노드
  • descendant(자손) : 어떤 노드의 아래쪽으로 뻗어 나간 모든 노드
  • level(레벨) : 루트로부터 얼마나 떨어져 있는지를 나타낸 값, 루트는 레벨 0
  • degree(차수) : 노드가 갖는 자식의 수, 모든 노드의 차수가 n 이하인 트리를 'n진 트리'라고 함(위의 그림은 2진 트리)
  • height(높이) : 루트에서 가장 멀리 떨어진 노드까지의 거리(리프 레벨의 최댓값)
  • subtree(서브트리) : 트리 안에서 어떤 노드를 루트로 정하고 그 자손으로 이루어진 트리
  • null tree(널 트리) : 노드가 전혀 없는 트리

 

 

 

▶️ 순서 트리(Ordered tree)와 무순서 트리(Unordered tree)

 

형제 노드의 순서를 따지면 순서트리

형제 노드의 순서를 따지지 않으면 무순서 트리

 

 

 

▶️ 트리 = 상속

 

트리 관계를 보니까 자바의 상속이 떠오른다.

아무래도 모양이 상속관계랑 똑같은 것 같다.

상속에서 자식 클래스는 하나의 부모 클래스만 가질 수 있고

부모 클래스는 여러 자식 클래스를 가질 수 있다.

트리에서도 하나의 노드는 하나의 부모만 가질 수 있고

부모 노드는 여러 자식을 가질 수 있다.

상속 관계를 그림으로 그리면 트리 모양이다.

 

 

 

▶️ 순서 트리 탐색 방법 알아보기

 

순서 트리를 검색하는 방법에는 두가지 방법이 있다.

너비 우선 탐색(가로형 탐색)

깊이 우선 탐색(세로형 탐색)

 

 

 

 

❑ Graph

 

 

Graph

 

➤ 자료구조에서 그래프는 네트워크망 처럼 복잡하게 연결된 모양이다.

➤ 비선형 구조

 

※ 용어

  • vertex(정점) : 하나의 점
  • edge(간선) : 점들을 이어주는 선
  • 직접적인 관계(인접하다) : 두 점을 직접적으로 이어주는 선 존재
  • 간접적이 관계 : 두 점이 두개 이상의 선을 거쳐가야 하는 관계
  • weighted Graph(가중치 그래프) : 연결의 강도(추가적인 정보)가 있는 그래프
  • unweighted Graph(비가중치 그래프) : 연결의 강도가 없는 그래프
  • in-degree(진입차수) / out-degree(진출차수) : 한 정점에 진입하고 진출하는 간선이 몇 개인지
  • self loop (자기 루프) : 정점에 진출하는 간선이 곧바로 자기 자신에게 진입하는 경우 '자기 루프를 가졌다'고 표현
  • cycle (사이클) : 한 정점에서 출발하여 다시 해당 정점으로 돌아갈 수 있다면 '사이클이 있다'고 표현

 

 

▶️ 그래프의 종류

 

- undirected Graph(무방향 그래프): 간선의 방향이 없는 그래프

- directed Graph(방향 그래프): 간선에 방향성이 있는 그래프

- weighted Graph(가중치 그래프) : 연결의 강도(추가적인 정보)가 있는 그래프

- 루트 없는 트리: 간선을 통해 정점 간 잇는 방법이 한가지인 그래프

- 이분 그래프: 그래프의 간선을 두 그룹으로 나눈 후 다른 그룹끼리만 간선이 존재하게 분할할 수 있는 그래프

- 사이클이 없는 그래프: 정점에서 출발해 자기 자신으로 돌아오는 경로가 없는 그래프

 

 

▶️ 그래프의 표현

 

인접 행렬

 

- 인접행렬 : 2차원 배열로 정점 간의 간선의 존재 여부를 1 또는 0으로 표시

                  가중치 그래프일 경우 가중치로 표시하면 좋음

                  가장 빠른 경로를 찾고자 할 때 주로 사용

 

인접 리스트

 

- 인접 리스트 : 정점 개수만큼 리스트를 만들어 각각의 정점 리스트에 간선 추가

인접 행렬은 간선이 없는 경우에도 확인하기 때문에 간선이 적은 경우 간선이 있는 경우만 확인하는 인접 리스트보다 오래걸린다.

 

 

▶️ 그래프의 표현

 

관계 표현이 필요한 모든 경우에 사용 가능

활용도가 높음

- 최단 경로 탐색

- 소셜 네트워크 관계망

- 인터넷 네트워크망(전송속도 계산)

 

 

 

 

트리에 대해서 조사하다 보니까 지금 내가 보고 있는 건 빙산의 일각인 것 같다..

 

 

너무 욕심부려서 다 알려고 하지말고 궁금한건 그때그때 찾아보자

지금 공부하면 허우적거리다가 진도 못 따라갈 것 같다 ㅋㅋ

금요일에 배운것도 이틀이 지난 오늘에서야 정리한 걸 보면

내 속도가 많이 느리긴 하다.

Today I Learned인데..

이번건 Two days ago I Learned의 TIL이다..

 

 

 

 

읽어주셔서 감사합니다. 도움이 되셨기 바랍니다.

오개념에 대한 지적은 언제나 환영입니다~

 

 

 

 

 

 

 

자료구조에서 중요한 개념인 Stack과 Queue를 코드로 직접 구현해보았다.

 

'Doit! 자료구조와 함께 배우는 알고리즘 입문 자바편'을 참고하여 작성하였습니다.

 

 

❑ 자바에서 Stack 배열로 구현하기

 

더보기
public class IntStack {
    private int[] stk;  // 스택용 배열
    private int capacity;   // 스택 용량
    private  int ptr;   // 스택 포인터

    // 실행시 예외: 스택이 비어있음
    public class EmptyIntStackException extends RuntimeException {
        public EmptyIntStackException(){};
    }

    // 실행시 예외: 스택이 가득 참
    public class OverflowIntStackException extends RuntimeException {
        public OverflowIntStackException(){};
    }

    // 생성자
    public IntStack(int maxlen) {
        ptr = 0;
        capacity = maxlen;
        try {
            stk = new int[capacity];
        } catch (OutOfMemoryError e) {
            capacity = 0;
        }
    }

    // 스택에 x push
    public int push(int x) throws OverflowIntStackException {
        if(ptr >= capacity) {
            throw new OverflowIntStackException();
        }
        return stk[ptr++] = x;
    }

    // 스택에서 데이터를 pop
    public int pop() throws EmptyIntStackException {
        if(ptr <= 0) {
            throw new EmptyIntStackException();
        }
        return stk[--ptr];
    }

    // 스택에서 데이터를 peek
    public int peek() throws EmptyIntStackException {
        if(ptr <= 0) {
            throw new EmptyIntStackException();
        }
        return stk[ptr - 1];
    }

    // 스택을 비움
    public void clear() {
        ptr = 0;
    }

    // 스택에서 x를 찾아 인덱스(없으면 -1)을 반환
    public int indexOf(int x) {
        for(int i = ptr - 1; i >= 0; i--) {
            if (stk[i] == x)
                return i;
        }
        return -1;
    }

    // 스택의 용량 반환
    public int getCapacity() {
        return capacity;
    }

    // 스택에 쌓여 있는 데이터 개수를 반환
    public int size() {
        return ptr;
    }

    // 스택이 비어있는가?
    public boolean isEmpty() {
        return ptr <= 0;
//        return ptr == 0; 으로도 가능
    }

    // 스택이 가득 찼는가?
    public boolean isFull() {
        return ptr >= capacity;
    }

    // 스택 안의 모든 데이터를 bottom-> top 순서로 출력
    public void dump() {
        if(ptr <= 0) {
            System.out.println("Stack is Empty!");
        }
        else {
            for(int i = 0; i < ptr; i++)
                System.out.print(stk[i]+ " ");
            System.out.println();
        }
    }
}

 

 

❑ Stack 클래스 테스트 하기

 

더보기
import java.util.Scanner;

public class IntStackTester {
    public static void main(String[] args) {
        Scanner stdIn = new Scanner(System.in);
        IntStack s = new IntStack(64);

        while(true) {
            System.out.println();
            System.out.printf("present data count: %d / %d\n", s.size(), s.getCapacity());
            System.out.print("(1)push (2)pop (3)peek (4)dump (5)clear (6)getCapacity (7)indexOf (8)isFull (0)exit  : ");

            int menu = stdIn.nextInt();

            if(menu == 0) break;    //exit

            int x;
            switch(menu) {
                case 1 :    // push
                    System.out.print("데이터: ");
                    x = stdIn.nextInt();
                    try {
                        s.push(x);
                    }catch (IntStack.OverflowIntStackException e) {
                        System.out.println("Stack is full!!");
                    }
                    break;
                case 2:     // pop
                    try {
                        x = s.pop();
                        System.out.println("pop data is " + x + ".");
                    } catch (IntStack.EmptyIntStackException e) {
                        System.out.println("Stack is Empty!!");
                    }
                    break;
                case 3:     // peek
                    try {
                        x = s.peek();
                        System.out.println("peek data is " + x + ".");
                    } catch (IntStack.EmptyIntStackException e) {
                        System.out.println("Stack is Empty!!");
                    }
                    break;
                case 4:     // dump
                    s.dump();
                    break;
                case 5:     // clear
                    s.clear();
                    break;
                case 6:     // getCapacity
                    System.out.println("Capacity : " + s.getCapacity());
                    break;
                case 7:     //indexOf
                    System.out.print("input you want to find : ");
                    x = stdIn.nextInt();
                    System.out.println(x + " is in " + s.indexOf(x));
                    break;
                case 8:
                    System.out.println("Stack is Full? " + s.isFull());
                    break;
            }

        }
    }
}

 

 

 

❑ 자바에서 Queue 배열로 구현하기

더보기
public class IntQueue {
    private int[] que;      // 큐용 배열
    private int capacity;   // 큐의 용량
    private int front;      // 맨 앞의 요소 커서
    private int rear;       // 맨 뒤의 요소 커서
    private int num;        // 현재 데이터 개수

    // 실행 시 예외 : 큐가 비어 있음
    public class EmptyIntQueueException extends RuntimeException {
        public EmptyIntQueueException() {}
    }

    // 실행 시 예외 : 큐가 꽉 차있음
    public class OverflowIntQueueException extends RuntimeException {
        public OverflowIntQueueException() {}
    }

    // 생성자
    public IntQueue(int maxlen) {
        num = front = rear = 0;
        capacity = maxlen;
        try {
            que = new int[capacity];        // 큐 본체용 배열을 생성
        } catch (OutOfMemoryError e) {      // 생성할 수 없을 경우
            capacity = 0;
        }
    }

    // 큐에 데이터 인큐
    public int enque(int x) throws OverflowIntQueueException {
        if(num >= capacity) {
            throw new OverflowIntQueueException();  // 큐가 가득참
        }
        que[rear++] = x;
        num++;
        if(rear == capacity)
            rear = 0;
        return x;
    }

    // 큐에 데이터를 디큐
    public int deque() throws EmptyIntQueueException {
        if(num <= 0)
            throw new EmptyIntQueueException();
        int x = que[front++];
        num--;
        if(front == capacity)
            front = 0;
        return x;
    }

    // 큐에서 데이터를 피크(front 데이터를 들여다봄)
    public int peek() throws EmptyIntQueueException {
        if(num <= 0)
            throw new EmptyIntQueueException();    // 큐가 비어있음
        return que[front];
    }

    // 큐를 비움
    public void clear() {
        num = front = rear = 0;
    }

    // 큐에서 x를 검색하여 인덱스(찾지 못하면 -1)를 반환
    public int indexOf(int x) {
        for(int i = 0; i < num; i++) {
            int idx = (i + front) % capacity;
            if(que[idx] == x)       // 검색 성공
                return idx;
        }
        return -1;      // 검색 실패
    }

    // 큐의 용량을 반환
    public int getCapacity() {
        return capacity;
    }

    // 큐에 쌓여 있는 데이터 개수를 반환
    public int size() {
        return num;
    }

    // 큐가 비어있나요?
    public boolean isEmpty() {
        return num <=0;
    }

    // 큐가 가득 찼나요?
    public boolean isFull() {
        return num >= capacity;
    }

    // 큐에서 임의의 데이터를 검색
    public int search(int x) {
        for(int i = 0; i < num; i++) {
            if(que[(i + front) % capacity] == x)
                return i + 1;
        }
        return 0;
    }

    // 큐 안의 모든 데이터를 front -> rear 순으로 출력
    public void dump() {
        if(num <= 0)
            System.out.println("큐가 비어있습니다.");
        else {
            for(int i = 0; i < num; i++) {
                System.out.println(que[(i + front) % capacity] + " ");
            }
            System.out.println();
        }
    }
}

 

 

❑ Queue 클래스 테스트하기

 

더보기
public class IntQueue {
    private int[] que;      // 큐용 배열
    private int capacity;   // 큐의 용량
    private int front;      // 맨 앞의 요소 커서
    private int rear;       // 맨 뒤의 요소 커서
    private int num;        // 현재 데이터 개수

    // 실행 시 예외 : 큐가 비어 있음
    public class EmptyIntQueueException extends RuntimeException {
        public EmptyIntQueueException() {}
    }

    // 실행 시 예외 : 큐가 꽉 차있음
    public class OverflowIntQueueException extends RuntimeException {
        public OverflowIntQueueException() {}
    }

    // 생성자
    public IntQueue(int maxlen) {
        num = front = rear = 0;
        capacity = maxlen;
        try {
            que = new int[capacity];        // 큐 본체용 배열을 생성
        } catch (OutOfMemoryError e) {      // 생성할 수 없을 경우
            capacity = 0;
        }
    }

    // 큐에 데이터 인큐
    public int enque(int x) throws OverflowIntQueueException {
        if(num >= capacity) {
            throw new OverflowIntQueueException();  // 큐가 가득참
        }
        que[rear++] = x;
        num++;
        if(rear == capacity)
            rear = 0;
        return x;
    }

    // 큐에 데이터를 디큐
    public int deque() throws EmptyIntQueueException {
        if(num <= 0)
            throw new EmptyIntQueueException();
        int x = que[front++];
        num--;
        if(front == capacity)
            front = 0;
        return x;
    }

    // 큐에서 데이터를 피크(front 데이터를 들여다봄)
    public int peek() throws EmptyIntQueueException {
        if(num <= 0)
            throw new EmptyIntQueueException();    // 큐가 비어있음
        return que[front];
    }

    // 큐를 비움
    public void clear() {
        num = front = rear = 0;
    }

    // 큐에서 x를 검색하여 인덱스(찾지 못하면 -1)를 반환
    public int indexOf(int x) {
        for(int i = 0; i < num; i++) {
            int idx = (i + front) % capacity;
            if(que[idx] == x)       // 검색 성공
                return idx;
        }
        return -1;      // 검색 실패
    }

    // 큐의 용량을 반환
    public int getCapacity() {
        return capacity;
    }

    // 큐에 쌓여 있는 데이터 개수를 반환
    public int size() {
        return num;
    }

    // 큐가 비어있나요?
    public boolean isEmpty() {
        return num <=0;
    }

    // 큐가 가득 찼나요?
    public boolean isFull() {
        return num >= capacity;
    }

    // 큐에서 임의의 데이터를 검색
    public int search(int x) {
        for(int i = 0; i < num; i++) {
            if(que[(i + front) % capacity] == x)
                return i + 1;
        }
        return 0;
    }

    // 큐 안의 모든 데이터를 front -> rear 순으로 출력
    public void dump() {
        if(num <= 0)
            System.out.println("큐가 비어있습니다.");
        else {
            for(int i = 0; i < num; i++) {
                System.out.println(que[(i + front) % capacity] + " ");
            }
            System.out.println();
        }
    }
}

 

 

 

 

 

 

 

코딩 독학할 때 백준 가입해놨다가

난이도가 있는 것 같아서 한두문제 풀고 손을 안댔었다.

그러다가 오늘 알고리즘시간에 너무 헤맸어서

알고리즘 연습의 중요성을 느꼈다ㅠㅠ

백준으로 알고리즘 연습해서 알고리즘 뇌를 발달시켜야 겠다..🧠

일단 오늘 풀었지만 틀린 문제를 기록으로 남겨놓는다.

 

 

<문제 링크>

 

https://www.acmicpc.net/problem/1085

 

1085번: 직사각형에서 탈출

한수는 지금 (x, y)에 있다. 직사각형은 각 변이 좌표축에 평행하고, 왼쪽 아래 꼭짓점은 (0, 0), 오른쪽 위 꼭짓점은 (w, h)에 있다. 직사각형의 경계선까지 가는 거리의 최솟값을 구하는 프로그램

www.acmicpc.net

 

 

 

<헤맨 과정>

더보기

문제만 보면 직사각형에 좌표 있고 복잡하게 풀어야할 것 같지만

쉽게 접근할 수 있다.

일단 각각 전체 가로세로 길이의 절반보다 (x, y)좌표가 같거나 클 경우를 변수에 담아두었다가 담은 가로, 세로 변수 2개중 더 작은 것을 출력하면 된다.

        if(x >= w / 2) {
            horizon = w - x;
        }
        else horizon = x;

        if(y >= h / 2) {
            length = h - y;
        }
        else length = y;

 

이렇게 조건문을 작성하면 예제의 코드는 잘 돌아가지만

틀렸다.

인텔리제이에서는 잘 돌아가는데 풀이를 제출할 때마다 자꾸 틀렸다고 해서 꽤나 고민했다..

그러다가 혹시 나누기 2 하는 과정에서 int 형이기 때문에 소수점은 버려져서 그런가?

싶어서 테스트 해보니까 그것 때문이었다.

2 3 5 9

이런식으로 입력하면

세로의 변수 length에는 3이 9의 절반인 4.5 보다 작기 때문에 3이 들어가지만

가로는 5의 절반인 2.5와 2를 비교해서 horizon에 2가 들어갈 줄 알았는데

int 형을 나누기 2 했기 때문에 2.5의 소수점을 버린 2와 2를 비교해서 horizon에 5 - 2인 3이 들어간다.

그래서 horizon과 length를 비교한 출력값이 3이 나오게 된다.

 

이럴 때 해결 방법이 2가지가 떠올랐다.

 

1. if문안의 조건문을 double로 형변환한 숫자끼리 비교

        if((double)x >= (double)w / 2) {
            horizon = w - x;
        }
        else horizon = x;

        if((double)y >= (double)h / 2) {
            length = h - y;
        }
        else length = y;

 

 

2. 등호를 없애준다

        if(x > w / 2) {
            horizon = w - x;
        }
        else horizon = x;

        if(y > h / 2) {
            length = h - y;
        }
        else length = y;

 

 

 

<내 최종 코드>

더보기
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int x = sc.nextInt();
        int y = sc.nextInt();
        int w = sc.nextInt();
        int h = sc.nextInt();

        int horizon, length;

        if((double)x < (double)w / 2) {
            horizon = x;
        }
        else horizon = w - x;

        if((double)y < (double)h / 2) {
            length = y;
        }
        else length = h - y;

        System.out.print(Math.min(horizon, length));
    }
}

 

 

 

 

 

🔎 백준의 유용한 기능

 

 

백준과 깃헙 연동하기(백준허브 확장자)

 

크롬 확장자 중에 백준허브를 추가(Add to Chrome)

깃헙 아이디와 Authenticate

Repository 추가 또는 기존 Repository 주소 입력해서 연동

백준에서 문제를 풀고 제출하면 자동으로 내 깃헙 repository에 commit이 됩니다!⭐️

 

 

 

백준 티어 보기

 

백준에는 티어가 존재하는줄 몰랐는데

브론즈, 실버, 골드,... 등 티어가 존재한다.

solved를 이용하면 백준에서 문제의 티어를 볼 수 있다.

 

 

이외에도 백준 티어를 깃헙 프로필에 보이게 할 수도 있다. ㅋㅋ

근데 아직 백준 티어가 브론즈 바닥이라서 나중에 당당하게 티어를 보여줄 만큼 올라갔을 때 깃허브 프로필에 올려야 겠다..

 

 

읽어주셔서 감사합니다.😍

 

 

 

 

❑ 람다식(Lambda)

 

 

➤ 람다식이란?

 

자바는 객체 지향 프로그래밍 언어이다.

객체 지향은 객체를 만들어야지만 함수(메서드)를 사용할 수 있지만

함수형 프로그래밍은 함수의 구현과 호출만으로 프로그램을 만들 수 있다.

자바가 처음 나온 90년대에는 객체 지향 언어가 각광받았지만 지금은 함수형 프로그래밍이 다시 각광받고 있다.

이런 패러다임에 맞춰서 자바 8버전부터 함수형 프로그래밍을 할 수 있도록 '람다식(Lambda Expressions)'을 지원한다.

 

자바에서 제공하는 함수형 프로그래밍 방식을 '람다식'이라고 한다.

람다식을 사용하면 간결하게 함수를 만들고 사용할 수 있다.

람다식은 먼저 인터페이스를 작성하고 사용하려는 인터페이스를 익명으로 구현하는 방법이다.

 

 

➤ 람다식의 작성

 

(매개변수) -> {실행코드}

 

 

람다식 문법 ⬇️

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

        x -> {System.out.println(x);}   // 매개변수가 한 개인 경우 괄호 생략 가능 ⭕️
        x, y -> {System.out.println(x * y);}  // 매개변수가 두 개 이상인 경우 괄호 생략 ❌
        (x, y) -> System.out.println(x * y);    // 실행문이 한 개인 경우 괄호 생략 가능 ⭕️
        (x, y) -> {return x + y;}   // 하지만 실행문이 한 개여도 return문인 경우 괄호 생략 ❌
        (x, y) -> x + y;        // 실행문이 한 개이고 return문인 경우 return과 중괄호 모두 생략 ⭕️

    }
}

 

<작성규칙>

매개변수가 하나일 경우는 괄호를 생략 가능하지만 2개 이상인 경우엔 생략할 수 없다.

실행문이 하나인 경우에도 중괄호를 생략할 수 있지만 하나인 실행문이 return문인 경우에는 생략할 수 없다.

실행문에 return문만 있는 경우는 return과 중괄호 모두 생략하는 것이 올바르다.

 

 

 

➤ 함수형 인터페이스

 

자바에서 메소드를 사용하려면 클래스를 만들고 객체롤 메소드를 호출해야 한다.

인터페이스는 클래스마다 공통적으로 사용하는 기능을 메소드마다 다르게 정의할 수 있게 해준다.

하지만 함수를 만들어서 사용할 때는 함수형 인터페이스를 만들면 같은 식에 다른 값을 넣어서 재사용할 수 있어 편리하다.

 

🌱 함수형 인터페이스를 작성하기

package lambdaPractice;

@FunctionalInterface
public interface BigNum {
    int getMax(int num1, int num2); // 추상 메서드 선언
//    int add(int num1, int num2);  // 함수형 인터페이스는 한 개의 추상 메서드만 선언할 수 있음!⚠️
}

 

위에는 두개의 숫자를 받아서 더 큰 숫자를 반환하는 함수를 포함한 함수형 인터페이스이다.

위에를 보면 작성하는 규칙이 있다.

1. '@FunctionalInterface' 애너테이션을 사용해서 컴파일러에 함수형 인터페이스임을 알릴 수 있다.

2. 한 개의 추상 메소드만 하나만 정의할 수 있다.

 

🌱 함수형 인터페이스 구현하기!

package lambdaPractice;

public class BigNumTest {
    public static void main(String[] args) {
        BigNum big = (x, y) -> (x >= y) ? x : y;
        System.out.println(big.getMax(10, 12));
        System.out.println(big.getMax(5,1));       
    }
}

 

작성한 함수형 인터페이스형인 BigNum형 big 함수를 선언해주었다.

매개변수로 x와 y를 받아서 'x >= y' 가 true이면 x, false이면 y를 반환하도록 만들었다.

 

 

 

➤ 왜 함수형 프로그래밍을 이용할까?

 

자바에서 람다식을 이용한 함수형 프로그램 코드가 객체를 만들어서 사용하는 객체지향형 코드보다 훨씬 간결하고 직관적이다.

함수식을 통해서 용도를 한눈에 이해할 수 있게 된다.

 

그리고 재사용하기도 용이하고 확장성 있는 프로그래밍이 가능하다.

함수형 인터페이스를 한번 구현하면 구현한 객체명으로 같은 식을 계속 사용할 수 있다.

만약 중간에 식을 조금 수정해야 한다면 다시 만들 필요없이 람다식을 이용해서 만들어 놓은 객체에 함수식을 고쳐주면 된다.

 

함수가 입력받은 자료 이외에 외부 자료에 영향을 미치지 않기 때문에 여러 자료를 동시에 처리하는 병렬 처리에 적합하다.

 

 

 

➤ 메서드 레퍼런스

 

함수형 인터페이스와 비슷한 기능을 제공하는 것이 메서드 레퍼런스이다.

메서드 레퍼런스는 작성해놓은 클래스의 객체를 생성하지 않고도 클래스 내의 메서드를 이용할 수 있게 해준다.

매개변수를 작성할 필요없이 메서드로 인수를 전달한다.

 

IntBinaryOperator operator1 = (a, b) -> Math.max(a, b);
IntBinaryOperator operator2 = Math :: max;
System.out.println(operator1.applyAsInt(1, 5) + ", " + operator2.applyAsInt(1, 5));

메서드 레퍼런스는 :: 을 이용해서 클래스(또는 인터페이스)의 메서드에 접근할 수 있다.

클래스명::메소드명

IntBinaryOperator은 두 개의 int 매개값을 받아 int값을 리턴하는 함수형 인터페이스이다.

추상형 메서드 이름은 applyAsInt 이다.

 

정적 메서드 레퍼런스

클래스 :: 메서드

 

인스턴스 메서드 레퍼런스

참조 변수 :: 메서드

 

참고 사이트

https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

 

Method References (The Java™ Tutorials > Learning the Java Language > Classes and Objects)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

 

 

➤ 매개변수의 메서드 레퍼런스

 

람다식에서 제공되는 메개변수의 메서드를 호출하여 매개변수를 매개값으로 사용하는 경우도 있다.

(a,b) -> {a.instanceMethod(b);};

클래스 :: instanceMethod

정적 메서드 레퍼런스와 작성방법은 동일하지만 a의 인스턴스 메서드가 참조된다는 점에서 다른 코드이다.

 

 

➤ 생성자 참조

 

생성자를 참조하는 람다식 표현도 작성 가능하다.

(a, b) -> {return new Object(a, b);};

 

위의 코드를 생성자 참조 표현으로 간단하게 표현할 수 있다.

클래스 :: new

 

 

 


 

 

❑ 스트림(Stream)

 

 

➤ 스트림이란?

 

스트림은 배열, 컬렉션의 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자 이다.

정렬이나 특정 값을 제외하고 출력하는 등 여러 자료 처리에 대한 기능을 구현해 놓은 클래스이다.

스트림을 사용하면 List, Set, Map, 배열 등 다양한 데이터 소스를 스트림형으로 변환하고 다양한 메서드를 제공한다.

 

처리해야하는 자료에 상관없이 같은 방식으로 메서드를 호출할 수 있기 때문에 자료를 추상화 했다고 표현한다.

 

 

➤ 스트림 생성하기

 

배열을 스트림으로 생성하기 : Arrays.stream(arr)

Collection 인터페이스에는 Stream<E> stream() 메서드가 정의되어 있기 때문에

Collection을 구현한 클래스에서는 모두 클래스명.stream()으로 스트림 클래스를 만들 수 있다.

int[] arr = new int[];
Arrays.stream(arr);		// 배열을 stream형으로 만들기
List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream();	// ArrayList를 stream형으로 만들기

 

 

 

➤ 스트림의 자주 사용하는 메서드

 

forEach() : 내부적으로 반복문이 수행됨

stream.forEach(s -> System.out.println(s));

 

filter() : 조건을 넣고 조건이 참인 경우만 추출

stream.filter(s -> s.length() >= 5);

 

map() : 요소들을 순회하여 다른 형식으로 변환

stream.map(c -> c * 2);

 

forEach() : 요소를 하나씩 꺼내는 기능, 요소를 하나씩 소모한다.

stream.forEach(System.out.println(s));

 

count() : 요소의 개수를 셈

sum() : 요소의 합을 구함

average() : 요소의 평균값

max(), main() : 요소의 최대값, 최소값을 구함

 

sorted() : 요소들을 정렬함

stream.sorted() 	// 오름차순으로 정렬
stream.sorted(Comparator.reverseOrder());	// 내림차순으로 정렬

 

match() : 요소들이 특정한 조건을 충족하는지 검사, boolean형으로 반환

stream.allMatch(a -> a%2 == 0);	// 모든 요소들이 조건을 만족하는지 조사
stream.anyMatch(a -> a%2 == 0); // 최소 한 개 이상의 요소가 조건을 만족하는지 조사
stream.noneMatch(a -> a%2 == 0); // 모든 요소들이 조건을 만족하지 않는지 조사

 

 

 

➤ 스트림의 특징

 

  • 자료의 대상과 관계없이 동일한 연산을 수행한다.
  • 한 번 생성하고 사용한(소모한) 스트림은 재사용 할 수 없다.
  • 스트림의 연산은 기존 자료를 변경하지 않는다.
  • 스트림의 연산은 중간 연산과 최종 연산이 있다.

 

 

-Optional<T> 에 대해서 더 공부 필요

 

 

읽어주셔서 감사합니다😍

오개념에 대한 지적은 환영입니다.

 

 

 

❑ 열거형(Enum)

 

 

➤ enum이란?

 

enumeration : 열거, 목록

관련이 있는 상수들의 집합

자바에서 final로 선언하는 상수(고정된 값)들을 집합으로 묶어 놓은 것이다.

enum형 상수도 객체이다

JDK 1.5 버전부터 생김

 

 

➤ 문법

 

enum 열거형이름 { 상수명1, 상수명2, 상수명3, ... }

 

enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

 

열거형의 원소들은 대소문자 모두 사용 가능하지만 상수는 대문자로 작성하는 관례가 있음

 

 

 

➤ 사용법

 

enum은 쉽게 말하자면 class 처럼 사용하면 된다.

class 자리에 enum을 작성하고

사용할 때 클래스의 static 변수를 참조하는 것과 같은 방법으로 사용하면 된다.

 

System.out.println(Day.MONDAY);

 

열거형의 객체를 생성해서 값을 할당할 수 있다.

 

Day today = Day.THURSDAY;
System.out.println(today);	// THURSDAY 출력

 

 

 

 

➤ 열거 객체 메소드

 

enum

name()

ordinal()

compareTo(비교값)

valueOf(String name)

values()

 

 

➤ enum을 사용하는 이유

 

상수를 하나씩 선언하는 것보다 코드가 단순, 가독성이 좋음

인스턴스 생성과 상속을 방지

키워드를 사용하기 때문에 구현의 의도가 열거임을 분명하게 나타낼 수 있음

 

 

 

➤ public static final의 문제점 ⚠️ 헷갈리는 개념

 

'public static final'을 이용해서 하나씩 상수를 선언하게 되면

-> 중복이 발생할 수 있다.

-> 이부분은 인터페이스에 상수를 작성해서 일차적으로 해결 가능하지만

인터페이스는 타입 안정성의 문제가 생긴다.

인터페이스를 사용해서 넣은 상수 값끼리 서로 비교 가능한 문제가 발생한다.

직접 비교해도 에러가 발생하지 않아서 타입안정성이 떨어지게 된다.

비교자체가 불가능하게 하려면 서로 다른 객체로 만들어야 한다.

이 부분은 공부해도 아직 설명할 정도로 이해하진 못했다. 추후에 공부필요!

객체를 따로따로 만들면 코드 안정성은 올라가지만 switch문에 사용은 못함

enum은 switch문에 사용 가능

 

 


 

 

❑ 애너테이션(Annotation)

 

주석과 비슷한 것

@ 로 나타냄

애너테이션은 주석처럼 프로그램에 영향을 미치지 않음

 

 

➤ 주석 vs 애너테이션

 

누구를 위한 정보인가?

주석: 코드를 읽는 사람

애너테이션: 프로그램

 

 

 

➤ 애너테이션의 용도

 

프로그램에 직접적으로 영향을 미치진 않으면서 컴파일러에게 정보를 제공해줄 때 사용한다.

예를 들어서 자주 쓰는 애너테이션인 @Override는 컴파일러에게 밑의 메소드가 상위 클래스의 메소드를 오버라이딩한 메소드라는 것을 알려준다.

그래서 메소드 이름의 오타같은 문제를 컴파일러가 잡아줄 수 있도록 해준다.

만약에 @Override로 컴파일러에 정보를 제공하지 않는다면 어떤 문제가 생길까?

만약에 상위 클래스에서 run()이라고 정의한 메소드가 있다고 생각하자.

그리고 하위 클래스에서 이 메소드를 재정의해서 작성하려는데 급하게 코딩하다가 rnu()이라고 오타를 냈다고 가정해보자.

이때 프로그램을 실행하게 되면 컴파일러는 이 rnu()이라는 오타난 메소드를 상위 클래스의 메소드를 재정의한 메소드가 아닌 새로 정의한  아예 새로운 메소드라고 인식한다.

이런 문제를 방지하기 위해 컴파일러에 @Override 라는 애너테이션으로 정보를 제공하고 컴파일러는 개발자가 잘못 작성한 부분을 오류로 알려주게 된다.

 

 

 

➤ 애너테이션의 종류

 

표준 애너테이션

자바에서 기본 제공하는 애너테이션

@Override : 컴파일러에게 메서드를 오버라이딩하는 것이라고 알림

@Deprecated : 앞으로 사용하지 않는다고 알림

@FunctionalInterface : 함수형 인터페이스임을 알림

@SuppressWarning : 컴파일러가 경고메세지를 나타내지 않음

 

 

메타 애너테이션

애너테이션에 붙는 애너테이션, 애너테이션을 정의하는데 사용

@Target : 애너테이션을 정의할 때 적용 대상을 지정하는데 사용

@Documented : 애너테이션 정보를 javadoc으로 작성된 문서에 포함시킴

@Inherited : 애너테이션이 하위 클래스에 상속되도록 함

@Repeatable : 애너테이션을 반복해서 적용할 수 있게 함

 

 

사용자 정의 애너테이션

사용자가 직접 정의하는 애너테이션

 

읽어주셔서 감사합니다 😍

오개념에 대한 지적은 언제나 환영입니다

 

 

 

 

 

❑ 내부 클래스(Inner Class) 란?

 

 

말 그대로 클래스 내부에 선언된 클래스

주로 클래스 내에서만 사용하고 다른 클래스에서는 사용하지 않을 때 내부 클래스로 선언함

 

class Outer {	// 외부 클래스
	class Inner {	// 내부 클래스
    		
    }
}

 

클래스를 감싸고 있는 클래스 : 외부 클래스

클래스안에 있는 클래스 : 내부 클래스

 

 

내부 클래스는 총 4가지 종류가 있음

 

내부 클래스 종류 선언 위치
인스턴스 내부 클래스 인스턴스 변수 위치
정적 내부 클래스 정적 변수 위치
지역 내부 클래스 지역 변수
익명 내부 클래스  

 

클래스내 변수(인스턴스 변수, 정적 변수, 지역 변수)의 위치와 선언 위치가 같다.

 

 

1. 인스턴스 내부 클래스( instance inner class)

 

인스턴스 변수가 선언되는 위치에 선언된다.

외부 클래스 내부에서만 생성하여 사용할 객체를 선언할 때 사용한다.

 

항상 외부 클래스가 먼저 생성된 후 인스턴스 내부 클래스가 생성된다.

따라서 외부 클래스 생성 없이는 사용 불가

 

인스턴스 내부 클래스에 static 변수, static 메소드를 선언할 수 없게 되어있어서 오류가 발생한다.

static 변수나 static 메소드는 클래스의 생성과 상관없이 사용할 수 있지만

내부 클래스는 외부 클래스가 생성되야만 생성되기 때문에 static 사용 불가

 

외부 클래스에서 private으로 선언한 것들 모두 내부 클래스에서 접근 가능

인스턴스 내부 클래스의 메소드는 외부 클래스의 메소드가 호출될 때 사용할 수 있다.

 

 

 

 

2. 정적 내부 클래스(static inner class)

 

인스턴스 내부 클래스는 정적 변수나 정적 메소드를 생성할 수 없다.

하지만 정적 내부 클래스는 정적 메소드, 정적 변수를 내부에 생성하고 사용할 수 있다.

따라서 외부 클래스의 생성과 상관없이 사용하거나 내부에 정적 변수, 정적 메소드를 사용하고 싶다면 정적 내부 클래스 사용하면 된다.

 

정적 내부 클래스는 외부 클래스의 멤버 변수와 같은 위치에 정의한다.

정적 변수와 마찬가지로 static 예약어를 사용한다.

 

예시 코드

더보기
public class OutClass {		// 외부 클래스
    private int num = 1;	// private으로 선언한 인스턴스 변수
    private static int sNum = 2;	// private으로 선언한 정적 변수

    static class InStaticClass {    // 정적 내부 클래스
        int inNum = 4;            // 정적 내부 클래스의 인스턴스 변수
        static int sInNum = 5;    // 정적 내부 클래스의 정적 변수

        void inTest() {
//            num += 10;		// 오류⚠️ (정적 내부 클래스에서는 외부 클래스의 인스턴스 변수 사용 불가)
            System.out.println("InStaticClass inNum = " + inNum + "(내부 클래스의 인스턴스 변수 사용)");
            System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 정적 변수 사용)");
            System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 정적 변수 사용)");
        }

        static void sTest() {
//            num += 10;		// 오류⚠️ (정적 내부 클래스에서는 외부 클래스의 인스턴스 변수 사용 불가)
//            inNum += 10;		// 오류⚠️ (정적 메소드에서는 인스턴스 변수 사용 불가)
            sInNum += 100;		// 내부 클래스의 정적 변수 사용 가능
            System.out.println("OuterClass sNum = " + sNum + "(외부 클래스의 정적 변수 사용)");
            System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 정적 변수 사용)");
        }
    }
}

class main {
    public static void main(String[] args) {
        OutClass.InStaticClass sInClass = new OutClass.InStaticClass();

        System.out.println("정적 내부 클래스 일반 메소드 호출");
        sInClass.inTest();
        System.out.println();
        System.out.println("정적 내부 클래스의 정적 메소드 호출");
        OutClass.InStaticClass.sTest();
    }
}

 

<출력 결과>

정적 내부 클래스 일반 메소드 호출
InStaticClass inNum = 4(내부 클래스의 인스턴스 변수 사용)
InStaticClass sInNum = 5(내부 클래스의 정적 변수 사용)
OutClass sNum = 2(외부 클래스의 정적 변수 사용)

정적 내부 클래스의 정적 메소드 호출
OuterClass sNum = 2(외부 클래스의 정적 변수 사용)
InStaticClass sInNum = 105(내부 클래스의 정적 변수 사용)

 

 

정적 내부 클래스 내의 일반 메소드와 정적 메소드는 변수 유형에 따라 사용가능 여부가 다르다.

표로 정리하면 아래와 같다.

 

정적 내부 클래스 메소드 변수 유형 사용 가능 여부
일반 메서드
void Method()
외부 클래스의 인스턴스 변수 x
외부 클래스의 정적 변수 o
정적 내부 클래스의 인스턴스 변수 o
정적 내부 클래스의 정적 변수 o
정적 메서드
static void sMethod()
외부 클래스의 인스턴스 변수 x
외부 클래스의 정적 변수 o
정적 내부 클래스의 인스턴스 변수 x
정적 내부 클래스의 정적 변수 o

 

정적 내부 클래스의 정적 메소드에서는 외부 클래스나 내부 클래스에서 정적 변수만 사용 할 수 있다.

 

 

 

➤ 다른 클래스에서 정적 내부 클래스 사용하기

 

정적 내부 클래스는 외부 클래스를 생성하지 않고도 내부 클래스 자료형으로 바로 선언하여 생성할 수 있다.

OutClass.InStaticClass sInClass = new OutClass.InStaticClass();

 

또한 정적 내부 클래스에 선언한 메소드나 변수는 private이 아닌 경우에 다른 클래스에서도 사용할 수 있다.

OutClass.InStaticClass.sTest();

 

 

 

 

2. 지역 내부 클래스(local inner class)

 

지역 변수와 같이 메소드 내부에 정의하는 클래스이다.

 

지역 내부 클래스 선언하기

더보기
class LocalTest {		// 외부 클래스
    int num = 10;
    static int sNum = 100;

    Runnable getMyPet(int pNum){		// 메소드
        int mNum = 20;
//        static int insNum = 200;  // 메소드내 정적 변수 선언 불가
        class MyPet implements Runnable{	// 🔥지역내부 클래스
            int inNum = 30;         // 컴파일러가 자동으로 final 추가
//            num += 10;        // 외부 클래스의 멤버변수 변경 불가
//            mNum += 10;       // 지역변수는 상수로 바뀌므로 변경하면 오류 발생
//            pNum += 10;       // 메소드의 지역변수는 상수로 바뀌므로 변경하면 오류 발생
            public void run() {
                System.out.println("외부 클래스의 멤버 변수 num : " + num);
                System.out.println("외부 클래스의 정적 변수 sNum : " + sNum);
                System.out.println("외부클래스의 메소드내 지역변수 mNum : " + mNum);
                System.out.println("지역 내부 클래스의 멤버 변수 inNum : " + inNum);
                System.out.println("지역 내부 클래스를 감싸고 있는 메소드의 매개변수 pNum : " + pNum);
            }
        }
        return new MyPet();
    }
}

 

만든 지역 내부 클래스는 이렇게 사용된다.

public class LocalInnerPractice {
    public static void main(String[] args) {
        LocalTest myPet = new LocalTest();
        Runnable runnable = myPet.getMyPet(10);
        runnable.run();
    }
}

 

<출력 결과>

외부 클래스의 멤버 변수 num : 10
외부 클래스의 정적 변수 sNum : 100
외부클래스의 메소드내 지역변수 mNum : 20
지역 내부 클래스의 멤버 변수 inNum : 30
지역 내부 클래스를 감싸고 있는 메소드의 매개변수 pNum : 10

 

 

지역내부 클래스를 생성하는건 생소하기도 하고 복잡하다.

일단은 외부 클래스를 반드시 만들어 줘야 하고

메소드를 실행해서 return으로 지역 내부 클래스 객체를 생성해서 반환하도록 설계해주면 된다.

 

 

 

※ 참고) 메소드 내의 지역 변수는 static 변수로 선언할 수 없다.

자바에서는 static local variable을 지원하지 않는다.

static local variables are not allowed!

static 변수는 field 에만 정의되는 변수이다.

메소드는 실행될 때만 stack 메모리에 올라갔다가 실행이 끝나면 stack메모리에서 사라진다.

그런데 정적 변수는 컴파일과 동시에 클래스 정보와 함께 메모리를 할당받고 프로그램 실행중에는 계속 메모리 공간에 있어서 접근할 수 있다.

따라서 자바에서 static 변수는 내부에 선언하면 오류가 발생한다.

 

참고 사이트

https://www.geeksforgeeks.org/g-fact-47/

 

Are static local variables allowed in Java? - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

 

지역 내부 클래스에서 지역 변수 = 상수 ?

 

지역 내부 클래스에서 사용하는 변수는 상수로 바뀌어서 값을 변경할 수 없게 된다.

그 이유를 이해하려면 일단 지역 내부 클래스의 지역변수가 JVM에서 메모리가 어떻게 흘러가는지를 알아야한다.

항상 지역 변수는 메소드가 호출될 때 stack 메모리에 생성되고 메소드 실행이 끝나면 메모리에서 사라진다.

그런데 만약에 지역변수를 지역 내부 클래스에서 일반 변수처럼 쓸 수 있다면 어떻게 될까?

다른 클래스에서 지역 내부 클래스를 만들기 위해 메소드를 호출하고

메소드는 스택 메모리에 올라갔다가 메소드 수행이 끝나면 스택 메모리에서 사라진다.

LocalTest myPet = new LocalTest();
Runnable runnable = myPet.getMyPet(10);	// getMyPet()메소드의 실행이 끝나면 스택메모리에서 사라짐
runnable.run();		// 지역내부 클래스의 메소드를 사용하면서 getMyPet()의 지역 변수가 사용됨

그런데 메소드의 매개변수와 지역변수를 지역 내부 클래스의 객체를 이용해서 스택메모리에서 사라진 이후에도 참조할 수 있다.

이 때 값을 변경할 수 있다면(변수라면) 문제가 생기기 때문에 지역 변수와 매개변수는 모두 상수로 처리된다.

상수 예약어 'final'을 작성해야하지만 안써도 컴파일러에서 자동으로 final을 추가해준다.

 

참고 사이트

https://www.geeksforgeeks.org/local-inner-class-java/

 

Local Inner Class in Java - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

지역 내부 클래스에서 사용하는 메소드의 지역 변수는 모두 상수로 바뀐다!

위에 거 다 까먹어도 이것만 기억하면 지역내부 클래스를 쓸 수 있다.

 

 

 

 

4. 익명 내부 클래스 (Anonymous inner class)

 

익명 클래스가 무엇인가?

클래스 이름을 사용하지 않는 클래스를 익명 클래스라고 한다.

 

지역 내부 클래스와 쓰임새가 비슷하지만 이름이 없기 때문에 일회용이다.

클래스의 선언과 동시에 객체를 생성하기 때문에 하나의 객체만을 생성한다.

따라서 한번만 사용하는 클래스를 생성할 때 만들면 된다.

 

지역 내부 클래스에서 사용한 코드를 익명 내부 클래스로 바꾸면 이렇게 작성할 수 있다.

더보기
class Person {
    Runnable getRunnable(int i) {
        int num = 100;

        return new Runnable() { // 익명 내부 클래스, Runnable 인터페이스 생성
            @Override
            public void run() {
//                num += 10;    // 오류 발생  
//                i += 20;      // 오류 발생

                System.out.println("매개변수 i : " + i);
                System.out.println("지역 변수 num : " + num);
            }
        };  // 끝나는 중괄호 뒤에 ; 붙임
               
    }
    Runnable runnable = new Runnable() {    // 익명 내부 클래스를 변수에 대입
        @Override
        public void run() {
            System.out.println("Runnable이 구현된 익명 클래스 변수");
        }
    };
}

 

생성한 익명 내부 클래스를 사용해보자

public class AnnoymousInnerTest {
    public static void main(String[] args) {
        Person p = new Person();
        Runnable runnable = p.getRunnable(10);
        runnable.run();
        p.runnable.run();
    }
}

 

<출력 결과>

매개변수 i : 10
지역 변수 num1 : 100
Runnable이 구현된 익명 클래스 변수

 

익명 클래스는 끝나는 부분에 세미콜론(;)을 작성해서 컴파일러에 익명 클래스가 끝났다고 알려줘야 한다.

 

익명 내부 클래스는 단 하나의 인터페이스나 단 하나의 추상 클래스를 바로 생성할 수 있다.

인터페이스는 인스턴스를 생성할 수 없는데..???

인터페이스의 인스턴스를 만들려면 구현을 다하지 않은 메소드를 작성해줘야 한다.

 

익명 내부 클래스를 호출할 때는 지역 내부 클래스 처럼

인스턴스를 생성해서 호출해도 되고

예시 코드의 'p.runnable.run()' 처럼 클래스내의 인터페이스 이름으로 바로 호출할 수도 있다.

 

익명 내부 클래스는 아직은 이해하기 어려운 개념이라는 생각이 든다. 🙁

익명 내부 클래스의 용도는 주로 안드로이드에서 버튼이나 텍스트 상자 같은 위젯(widget)의 이벤트를 처리하는 핸들러를 구현하는데 사용? 한다고 한다.

 

읽어주셔서 감사합니다.😃  도움이 되셨길 바랍니다.

오개념에 대한 지적은 언제나 환영입니다.🙌

 

 

 

1 2