자바 (13)

 

 

자연수에서 소수(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;
    }

 

 

 

 

읽어주셔서 감사합니다.

좋은 하루 되세요 ^^

+닉님 감사합니다😃

 

 

문제

 

박스에 짐을 담으려고 하는데 짐에는 무게가 있고 박스는 용량 제한이 있다.

박스에 짐을 2개까지만 담을 수 있다

이때 탐욕 알고리즘을 이용해서 짐을 박스에 담을 수 있는 최적의 선택을 했을 때 박스가 몇개 필요한가?

 

 

해결

 

import java.util.ArrayList;
import java.util.List;

public class movingStuff {
    public int movingStuff(int[] stuff, int limit) {
        int box = 0; // 박스에 넣은 짐 수(최대 2)
        int bstuff = 0; // 박스에 담은 짐 무게
        int result = 1; // 필요한 박수 수
        int max = 0;  // 남은 짐의 최댓값
        int maxIndex = 0; // 짐 최댓값의 인덱스
        List<Boolean> boo = new ArrayList<>();  // 짐을 확인했는지 확인할 배열 (모두 false)
        // 배열을 리스트로 변경
        ArrayList<Integer> slist = new ArrayList<Integer>();
        // 배열을 값을 리스트에 추가
        for (int i = 0; i < stuff.length; i++) {
            slist.add(stuff[i]);
            boo.add(false);
        }
        // 짐 리스트를 모두 포장할 때까지 반복
        while (!slist.isEmpty()) {
            // 한 번 확인한 max는 초기화
            max = 0;
            // 만약 다음 최대값을 가져오는데
            for (int i = 0; i < slist.size(); i++) {
                if (max < slist.get(i) && boo.get(i) == false) {
                    max = slist.get(i);
                    maxIndex = i;
                }
            }
            // 확인한 가장 무거운 짐은 true로 변경
            boo.set(maxIndex, true);
            // 짐 배열에서 가장 큰 값 가져옴
            // 가져온 제일 무거운 짐을 넣었을 때 limit가 넘지 않을 지 확인
            if (bstuff + max <= limit) {
                // 안넘으면 박스에 추가
                bstuff += max;
                box++;
                // 넣은 짐은 리스트에서 제거
                slist.remove(maxIndex);
                boo.remove(maxIndex);
                if(slist.isEmpty()) break;
                // 추가한 후에 박스안의 짐이 2개가 되면
                if (box == 2) {
                    // 박스 변수를 비움
                    box = 0;
                    bstuff = 0;
                    // 아직 담을 짐이 있다면 박스 새로 추가
                    result++;
                    // boo 초기화
                    for (int k = 0; k < boo.size(); k++) {
                        boo.set(k, false);
                    }
                }
            } else if (!boo.contains(false)) {
                // 새로운 박스 가져옴
                box = 0;
                bstuff = 0;
                // 아직 담을 짐이 있다면 박스 새로 추가
                result++;
                // boo 초기화
                for (int k = 0; k < boo.size(); k++) {
                    boo.set(k, false);
                }
            }
        }
        return result;
    }
}

 

 

 

 

 

 

 

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이다..

 

 

 

 

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

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

 

 

 

 

 

 

<문제 링크>

 

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

 

1018번: 체스판 다시 칠하기

첫째 줄에 N과 M이 주어진다. N과 M은 8보다 크거나 같고, 50보다 작거나 같은 자연수이다. 둘째 줄부터 N개의 줄에는 보드의 각 행의 상태가 주어진다. B는 검은색이며, W는 흰색이다.

www.acmicpc.net

 

 

 

<헤맨 과정>

더보기

먼저 직사각형의 크기를 입력받고

입력받은 크기만큼의 char형 2차원 배열을 만들어주었다.

그리고 그 안에 W또는 B을 2차원 크기만큼 입력받게 작성했다.

배열에서 입력받는 과정에 Scanner는 char형으로 받는 메소드가 따로 없어서

한 줄(한 행) 씩 String형으로 입력받고 그것을 2차원 배열의 안쪽 배열에 하나씩 대입하도록 만들어주었다.

그렇게 완성된 직사각형에서 자른 후 색을 바꿔서 8x8의 체스판을 만들도록 할 때 최소로 색을 바꾸는 경우를 찾아주기 위해서

배열안에서 만들 수 있는 모든 8x8을 조사하는 코드를 짜고 그 안에서 1행1열의 색이  B일 경우와 W일 경우로 나눠주었다.

그리고 B경우와 W경우에서 몇 개의 색깔을 바꿔야하는지 각각 카운트 해주고

한개의 체스판을 다 확인하면 더 작은 값을 최솟값을 구할 변수에 넣어주었다.

그리고 계속 이 과정을 반복하면서 이전의 최솟값, B경우, W경우 중 더 작은 값을 찾아주었다.

 

논리적으로는 정상적으로 잘 동작하는 코드를 짰는데

백준에서 돌려보니까 런타임 에러가 났다..

그래서 고민하다가 나중에 풀어야지 하고 두고 어제 다시 잡아봤다.

오랜만에 푹자고 맑은 정신에서 코드를 보니까

바로바로 틀린 부분이 눈에 보였다.

한시간씩 이거만 봐도 안보이더니 나중에 보니까 바로 에러를 찾아내다니...

직사각형의 크기를 입력받는 순서를 잘못 알고 있었고,

체스판의 한 줄이 바뀔 때마다 B와 W가 바뀌는 것을 안짰다.

이것만 고치니까 바로 맞았습니다! 가 떴다.

 

전에 암만해도 못 찾던 에러도 나중에 찾을 수도 있다는 경험을 했다.

코딩할 때 아무리 안 풀려도 포기하지 말자..!

 

 

 

 

<내 최종 코드>

 

https://github.com/WiseJade/Baekjoon-Auto-Push/blob/main/%EB%B0%B1%EC%A4%80/Silver/1018.%E2%80%85%EC%B2%B4%EC%8A%A4%ED%8C%90%E2%80%85%EB%8B%A4%EC%8B%9C%E2%80%85%EC%B9%A0%ED%95%98%EA%B8%B0/%EC%B2%B4%EC%8A%A4%ED%8C%90%E2%80%85%EB%8B%A4%EC%8B%9C%E2%80%85%EC%B9%A0%ED%95%98%EA%B8%B0.java

 

GitHub - WiseJade/Baekjoon-Auto-Push: This is a auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https:

This is a auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - GitHub - WiseJade/Baekjoon-Auto-Push: This is a auto push reposito...

github.com

 

 

 

밑의 글의 가장 하단에 백준과 깃헙을 연동할 수 있 유용한 정보를 작성해놨습니다. 도움이 되길 바랍니다.❤️

 

2022.05.25 - [JAVA/해결한 문제] - baekjoon-1085번 "직사각형 탈출" 회고

 

baekjoon-1085번 "직사각형 탈출" 회고

코딩 독학할 때 백준 가입해놨다가 난이도가 있는 것 같아서 한두문제 풀고 손을 안댔었다. 그러다가 오늘 알고리즘시간에 너무 헤맸어서 알고리즘 연습의 중요성을 느꼈다ㅠㅠ 백준으로 알고

wjcodding.tistory.com

 

 

 

 

 

 

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

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

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

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

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

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

 

 

<문제 링크>

 

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)의 이벤트를 처리하는 핸들러를 구현하는데 사용? 한다고 한다.

 

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

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

 

 

 

 

 

 

❑ 제네릭(Generic)

 

 

➤ 제네릭이 무엇인가?

 

  • Generic 은 “일반적인”이란 뜻
  • 클래스 내부에 사용할 타입을 외부에서 파라미터 형태로 지정
  • 데이터 타입을 일반화 ( ▶️ 범용적으로 사용할 수 있게함)
  • 자바 5부터 제네릭 추가됨

 

 

 

➤ 제네릭 프로그래밍

 

어떤 변수가 하나의 참조 자료형이 아닌 여러 참조 자료형을 사용할 수 있도록 프로그래밍하는 것

 

여러 참조 자료형이 쓰일 수 있을 때 미리 특정한 자료형을 지정하지 않고 제네릭으로 선언해서 클래스나 메소드가 사용되는 시점에 어떤 자료형 사용할지 지정

 

 

 제네릭 클래스 정의하기

 

클래스명 <T> {

}

 

ex) 

class GenericDeclaration <T> {
    private T variable;

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }
}

 

여러 자료형으로 바꾸어 사용할 부분에 자료형 대신 T 작성

<> : 다이아몬드 연산자

T를 자료형 매개변수(Type parameter, 타입 매개변수)

T : 대입된 자료형

GenericDeclaration <T> : 제네릭 자료형, 매개변수화된 자료형

 

 

ArrayList<Integer> array = new ArrayList<>();

선언하는 부분의 <>에 자료형을 작성했기 때문에 생성부분의 <> 안은 생략 가능

컴파일러가 유추 가능하기 때문

 

주로 <>안에 T를 많이 사용 (Type)

E (Element), K (Keyt), N (Number), V (Value), R (Result) 등 있음

위의 예시 뿐만 아니라 A, B, C 등 사용자가 정의해서 사용해도 된다.

 

 

 

⤵️아래는 저의 견해입니다.

 

타입 매개변수는 수학의 변수 x라고 생각해주면 코드를 작성하고 읽기가 용이하다.

수학에서 변수 x는 어떤 수든 대입할 수 있다.

하지만 식에서 x에 특정 수를 대입하는 순간 식 안의 모든 x는 그 수로 정해져서 계산된다.

예를 들어서 x+1-2x 라는 식이 있다고 가정해보자.

이때 x에 1을 대입하려고 하면 x, -2x 모두 x=1을 대입하고 계산해야 한다.

x에는 x=1 을 대입하고 -2x에는 x=2를 대입하는 식으로 계산해선 안된다.

변수 x는 같은 수를 의미하므로 모든 x엔 같은 수로 대입해줘야 한다.

프로그래밍에서 제네릭을 컴파일하는 과정에서도 마찬가지이다.

컴파일러는 같은 알파벳의 타입 매개변수는 모두 같은 변수라고 생각한다.

따라서 타입 매개변수 T는 인스턴스가 만들어질 때 같은 타입이 대입된다.

 

 

 

➤ static 변수와 타입 매개변수

 

static 변수, static 메소드 내에서는 타입 매개변수 사용할 수 없음

 

🔎  이유는?

제네릭의 타입이 정해지는 것은 인스턴스가 생성되는 순간이다.

하지만 static은 클래스 생성 시 클래스 정보와 함께 메모리에 올라가는데 타입을 지정해주지 않으면 메모리를 할당할 수 없다.

따라서 타입 매개변수의 자료형이 결정되는 시점보다 static 변수나 메소드가 생성되는 시점이 더 빠르기 때문에 문제가 발생하게 된다.

 

 

 

 

➤ 왜 제네릭이 직접 형 변환하는 것보다 안정적일까?

 

Object 등의 상위 클래스 타입으로 정의하고 직접 형변환 해주면 제네릭보다 불안정함

제네릭 클래스는 컴파일러가 일단 대입된 자료형이 잘 쓰였는지 확인하고

class 파일을 생성할 때 타입 매개변수를 사용한 곳에 지정한 자료형에 따라 컴파일함

 

 

 

➤ 제네릭 메소드(Generic Method)

 

클래스 전체가 아닌 클래스 내부의 특정 메소드만 제네릭으로 선언

매개변수나 메소드 시그니처에 타입 매개변수 T를 사용한 메소드

메소드가 호출되는 시점에 제네릭 타입이 결정됨

정의되는 시점에는 어떤 타입이 입력되는지 알 수 없음

-> length()와 같은 클래스내 메소드는 정의하는 시점에 사용 불가

-> 최상위 클래스 Object의 메소드는 사용 가능

일반 메소드, static 메소드에서 모두 활용 가능

 

 

 

➤ 제네릭의 장점

 

- 여러 참조 자료형마다 코드를 작성하지 않아도 되서 코드가 간결하고 직관적

- 매번 형 변환을 할 필요가 없기 때문에 오류의 확률 줄어듬 -> 안정성

- 새로운 자료형을 사용할 땐 그 자료형만 생성하고 사용할때 <> 안에 자료형 넣기만 하면됨

 

 

 

☑️ 참고) 제네릭으로 자료형 추론하기

자바10 부터는 지역변수에 한해서 자료형을 추론 가능(var형 사용 가능)

➡️ 지역 변수 자료형 추론(local variable type inference)

var local = new ArrayList<String>();

➡️ 컴파일러가 자료형 추론 가능

 

 

 

➤ 와일드카드(wild card)

 

제한을 두지 않는 기호를 의미

제네릭에서는 ? 기호를 사용하여 와일드 카드 사용

 

  • <?> : 타입 매개변수에 모든 타입 사용
  • <? extends T> : T 타입과 T타입의 하위 클래스만 사용
  • <? super T> : T 타입과 T 타입의 상위 클래스만 사용

 

 

 

추가로 공부할 부분

 

왜 Object가 아닌 Generic을 사용하는가?

 

 

Object의 메소드

https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

 

Object (Java Platform SE 7 )

Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup. The general contract of fi

docs.oracle.com

 

 

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

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

 

 

 

< 다형성(Polymorphism) >

 

 

❏ 다형성이 무엇인가?

 

polymorphism은 poly(여러개) 와 morphism(형태)의 합성어이다.

말그대로 여러개의 형태를 의미한다.

객체지향에서 하위 클래스는 상위클래스로 형변환(업캐스팅)이 가능하다.

 

 

예를 들어서 동물이라는 클래스를 개, 고양이, 도마뱀이라는 클래스가 상속하고 있다.

 

class Animal {
    void info(){
        System.out.println("나는 동물 입니다.");
    }
}

class Dog extends Animal{
    @Override
    void info() {
        System.out.println("나는 개입니다.🐕");
    }
    void woof() {
        System.out.println("멍멍멍멍");
    }
}

class Cat extends Animal {
    @Override
    void info() {
        System.out.println("나는 고양이입니다.🐱");
    }
    void meow() {
        System.out.println("야옹~야옹~");
    }
}

class Lizard extends Animal {
    String sexuality;
    @Override
    void info() {
        System.out.println("나는 도마뱀입니다.🦎");
    }
    
}

 

Animal의 info클래스를 각각의 하위 클래스에서 이름에 맞게 재정의를 해줬다.

 

public class AnimalTest {
    public static void main(String args[]) {
        // 다형성을 이용하여 같은 참조변수로 선언하기
        Animal dog = new Dog();
        Animal cat = new Cat();
        Animal lizard = new Lizard();

        // 재정의한 메소드 실행
        dog.info();
        cat.info();
        lizard.info();
    }
}

 

<출력결과>

나는 개입니다.🐕
나는 고양이입니다.🐱
나는 도마뱀입니다.🦎

 

상위 클래스인 Animal로 타입을 지정하고 new로 만든 인스턴스는 각각 하위 클래스로 지정해줄 수 있다.

이렇게 하위 클래스로 만들고 상위 클래스로 타입을 바꾸는 것을 '업캐스팅'이라고 한다.

상속 계층도에서 위로 올라가서 업캐스팅이라고 생각하자. (반대는 아래로 가니까 다운캐스팅)

 

 

업캐스팅, 다운캐스팅

 

자바에서는 업캐스팅은 별다른 키워드 없이 암묵적으로 가능하다.

이것을 '묵시적 형변환' 이라고도 한다.

하지만 반대로 암묵적으로 다운캐스팅(상위클래스 ➡️ 하위클래스)은 불가하다!!

 

다운 캐스팅 오류

상위 클래스로 인스턴스를 만든 후에 하위 클래스로 다운 캐스팅하게 되면 사진처럼 빨간 줄이 그어지고 오류가 난다.

 

 

 

 

❏ 왜 업캐스팅은 묵시적으로 되지만 다운캐스팅은 안될까?

 

하위 클래스들은 상위 클래스보다 구체적이고 메소드나 멤버변수가 같거나 많다.

하지만 상위 클래스는 더 일반적으로 만들어서 메소드나 멤버변수가 같거나 적다.

 

그래서 상위 클래스로 만든 인스턴스를 하위 클래스 타입의 메모리 공간에 집어넣으면

일부 메소드와 멤버변수를 사용할 수 없는 일이 발생한다.

실제로 클래스 내에 정의된 멤버변수와 메소드를 쓸 수 없어서 에러가 발생한다.

 

반대로 하위 클래스로 만든 인스턴스를 상위 클래스 타입의 메모리에 넣는건

인스턴스 만들 떄 있던 일부 기능이나 속성을 사용하지는 못하더라도 상위 클래스에 정의된 모든 메소드나 멤버변수는 사용할 수 있다.

내용물은 하위 클래스더라도 참조 타입은 상위 클래스이기 때문에 문제될 것은 없는 것이다.

 

참고) 상위 클래스 위의 상위 클래스들도 묵시적으로 형변환이 가능할까?

상속 계층이 여러 층일 때 여러 층 위의 상위 클래스로의 형변환도 묵시적으로 이루어진다.

 

 

 

❏ instanceof 를 통한 다운 캐스팅

 

상위 클래스로 형변환 했던 하위 클래스를 다시 원래 자료형으로 돌려놓을 수는 없을까?

하위 클래스로 직접적으로 형변환 할 수는 없지만 instanceof라는 키워드를 사용하면 원래 형태가 하위 클래스인지 확인할 수는 있다.

 

instanceof 예약어는 왼쪽에 있는 변수의 원래 인스턴스 형이 오른쪽 클래스 자료형인지 확인해준다.

➡️ 원래 자료형이 맞으면 true / 틀리면 false 를 반환한다.

주로 if 문과 함께 사용한다.

 

하위 클래스를 업 캐스팅하면 상위 클래스에 없는 해당 클래스의 고유한 메소드나 필드는 사용할 수 없게 된다.

위에 작성한 예시 코드에서 Dog와 Cat 클래스에는 상위 클래스에서 재정의한 메소드가 아닌 woof()와 meow()메소드가 있다.

그리고 Lizard에는 스트링 타입의 성별 멤버 변수가 선언되있다.

상위 클래스에서 정의 하지 않은 woof()와 meow(), sexuality를 업캐스팅한 변수에서 접근해보았다.

 

public static void main(String args[]) {
        // 다형성을 이용하여 같은 참조변수로 선언하기
        Animal dog = new Dog();
        Animal cat = new Cat();
        Animal lizard = new Lizard();
        Lizard example = new Animal();

        // 하위클래스에만 있는 메소드 실행
        dog.woof(); // cannot find symbol 오류
        cat.meow(); // cannot find symbol 오류
        // 하위 클래스에만 있는 필드 실행
        lizard.sexuality = "female";    // cannot find symbol 오류
        
}

 

실행하게 되면 'cannot find symbol' 에러가 뜨게 된다.

상위 클래스로 형변환하면서 하위 클래스에만 있던 메소드와 필드는 메모리 할당이 되지 않아 사용할 수 없게 된 것이다.

 

이럴 때 instanceof 를 사용해서 원래 자료형을 확인하고 맞으면 원래 인스턴스형강제로 다운캐스팅 해주면 된다.

원래 인스턴스형으로 변환할 때는 괄호안에 원래 자료형을 반드시 명시해야한다.

상위 클래스로 형변환 했던 객체를 다시 원래 자료형으로 형변환하는 것을 '다운캐스팅'이라고 부르기도 한다.

 

public static void main(String[] args) {
   // 상위 클래스로 형변환
   Animal dog = new Dog();
   Animal cat = new Cat();
   Animal lizard = new Lizard();

   // instanceof를 이용한 다운캐스팅
   if (dog instanceof Dog) {
       Dog dog1 = (Dog)dog;
       dog1.woof();
   }
   if (cat instanceof Cat) {
       Cat cat1 = (Cat)cat;
       cat1.meow();;
   }
   if (lizard instanceof Lizard) {
       Lizard lizard1 = (Lizard)lizard;
       lizard1.sexuality = "female";
       System.out.println(lizard1.sexuality);
   }
}

 

<출력결과>

멍멍멍멍
야옹~야옹~
female

 

 

 

 

 

 

업캐스팅과 다운캐스팅에 대한 레퍼런스

https://www.geeksforgeeks.org/upcasting-vs-downcasting-in-java/

 

Upcasting Vs Downcasting 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

 

 

 

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

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

 

 

 

1 2