1. 공변성 (Covariance):
    • 공변성은 서브타입 관계에서 타입이 변할 때 같은 방향으로 변하는 특성을 말합니다. 즉, A가 B의 서브타입인 경우, A 타입의 객체를 B 타입으로 변환할 수 있습니다.
    • 예를 들어, List<String>이 List<Object>의 서브타입이라면, List<String> 타입의 객체를 List<Object> 타입으로 변환할 수 있습니다. 이것은 배열의 공변성과 유사합니다.
  2. 불변성 (Invariance):
    • 불변성은 서브타입 관계에서 타입이 변하지 않는 특성을 말합니다. 즉, A가 B의 서브타입이더라도 A 타입의 객체를 B 타입으로 변환할 수 없습니다.
    • 예를 들어, List<String>이 List<Object>의 서브타입이 아니라면, List<String> 타입의 객체를 List<Object> 타입으로 변환할 수 없습니다.

 

 

 

결론 : 배열과 제네릭에는 매우 다른 타입 규칙이 적용된다. 배열은 공변이고 실체화되는 반면, 제네릭은 불공변이고 타입 정보가 소거된다. 그 결과 배열은 런타임에는 타입 안전하지만 컴파일타임에는 그렇지 않다. 제네릭은 반대다. 그래서 둘을 섞어 쓰기란 쉽지 않다. 둘을 섞어 쓰다가 컴파일 오류나 경고를 만나면, 가장 먼저 배열을 리스트로 대체하는 방법을 적용해보자.

비검사 경고(Unchecked Warning) : 컴파일러가 코드에서 타입 안전성을 검사하지 못하는 상황을 감지하여 발생하는 경고 메시지이다. 이 경고는 제네릭을 사용하는 코드에서 발생할 수 있는데, 일반적으로 제네릭을 사용하면서 발생하는 타입 캐스팅이나 원시 타입 사용 등의 상황에서 발생한다. 할 수 있는 한 모든 비검사 경고를 제거해야 한다.

 

비검사 경고를 제거할 수는 없지만 타입 안전하다고 확실할 수 있다면 @SuppressWarning("unchecked") 애너테이션을 달아 경고를 숨기자. 단 타입 안전함을 반드시 검증해야만 한다. 이 에너테이션을 사용했다면 그 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야만 한다. 이 애너테이션은 보통은 변수 선언, 아주 짧은 메서드, 혹은 생성자 등 가능한 한 좁은 범위에다가만 적용하자. 자칫 심각한 경고를 놓칠 수 있으니 절대로 클래스 전체에 적용해서는 안 된다.

 

결론 : 비검사 경고는 중요하니 무시하지 말자. 모든 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있는 잠재적 가능성을 뜻하니 최선을 다해 제거하라. 경고를 없앨 방법을 찾이 못하겠다면, 그 코드가 타입 안전함을 증명하고 가능한 한 범위를 좁혀 @SuppressWarnings("unchecked") 애너테이션으로 경고를 숨겨라. 그런 다음 경고를 숨기기로 한 근거를 주석으로 남겨라.

 

 

로 타입(raw type) : 제네릭 클래스나 인터페이스에서 타입 매개변수를 명시하지 않고 raw 타입으로 사용하는 것을 의미

 

제네릭 : 자바에서 컬렉션 클래스 및 메서드, 인터페이스 등을 작성할 때 타입을 파라미터화하는 기능

 

컬렉션 클래스 : 자바에서 데이터를 모으고 관리하는 데 사용되는 클래스들의 집합

 

//잘못된 예
List list = new ArrayList(); // 로 타입 사용

list.add("hello");
list.add(123);

String str = (String) list.get(0); // 형변환 필요
int num = (int) list.get(1); // 형변환 필요

//옳은 예
List<String> list = new ArrayList<>(); // 제네릭 사용

list.add("hello");
// list.add(123); // 컴파일 오류: 타입 불일치

String str = list.get(0); // 형변환 불필요

 

결론 : 로 타입을 사용하면 런타임에 예외가 일어날 수 있으니 사용하면 안된다.

톱 레벨 클래스 : 다른 클래스의 내부에 정의되지 않고, 자체 파일에 독립적으로 존재하는 클래스. 즉, 톱 레벨 클래스는 패키지 내에서 최상위에 위치하는 클래스이며, 다른 클래스의 멤버가 아닌 독립된 클래스.

 

아래 예시는 한 파일에 두 개의 클래스가 정의된것이다. 절대 따라하면 안되는 코드이다.

class Utensil {
	static final String NAME = "pan";
}

class Dessert {
	stattic final String NAME = "cake";
}

 

위와 같은 코드로 작성할 경우 오류가 발생 할 수 있다. 이를 해결하려면 단순히 톱 레벨 클래스들을 서로 다른 소스 파일로 분리해주면 된다. 굳이 여러 톱 레벨 클래스를 한 파일에 담고 싶다면 정적 멤버 클래스로 만들 수 있다.

public class Test {
	public static void main(String[] args) {
    	System.out.println(Utensil.NAME + Dessert.NAME);
    }
    
    private static class Utensil {
    	static final String NAME = "pan";
    }
    
    private static class Dessert {
    	static final String NAME = "cake";
    }
}

 

 

결론 : 소스 파일 하나에는 반드시 톱레벨 클래스(혹은 톱레벨 인터페이스)를 하나만 담자. 이 규칙만 따른다면 컴파일러가 한 클래스에 대한 정의를 여러 개 만들어내는 일은 사라진다. 소스 파일을 어떤 순서로 컴파일하든 바이너리 파일이나 프로그램의 동작이 달라지는 일은 결코 일어나지 않을 것이다.

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

 

14225번: 부분수열의 합

수열 S가 주어졌을 때, 수열 S의 부분 수열의 합으로 나올 수 없는 가장 작은 자연수를 구하는 프로그램을 작성하시오. 예를 들어, S = [5, 1, 2]인 경우에 1, 2, 3(=1+2), 5, 6(=1+5), 7(=2+5), 8(=1+2+5)을 만들

www.acmicpc.net

 

 

[정답 코드]

import java.io.*;
import java.util.*;

public class Main {
    static int[] arr, answer;
    static int N, max = 0;
    static boolean[] visited;
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        N = Integer.parseInt(st.nextToken());

        arr = new int[N];
        visited = new boolean[N];

        st = new StringTokenizer(br.readLine(), " ");
        for (int i = 0; i < N; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
            max += arr[i];
        }
        answer = new int[max + 2]; // 최대 가능한 합 + 2 로 설정해야 메모리 낭비를 막을 수 있다

        dfs(0, 0);

        for (int i = 1; i < answer.length; i++) {
            if (answer[i] == 0) { // 0일 경우 해당 인덱스는 나온적 없는 숫자이므로 출력
                System.out.println(i);
                break;
            }
        }

    }

    static void dfs(int depth, int sum) {
        if (depth == N) { // depth 가 N일 경우 종료
            answer[sum] = 1; // 현재까지 저장된 sum 값은 이미 나온 숫자이므로 1로 변경
            return;
        }
        dfs(depth + 1, sum + arr[depth]); // 현재까지 저장된 sum + 현재 인덱스의 arr 값
        dfs(depth + 1, sum); // 현재 인덱스의 arr 값을 추가하지않고 다음 인덱스 탐색
    }
}

 

[설명]

배열의 원소를 입력받을 때마다 값을 max에 추가해 나올 수 있는 가장 큰 자연수를 계산한다. 나올 수 있는 자연수들이 담긴 배열 answer = new int[max+ 2] 해준다. 다른 코드에서는 문제에서 주어진 나올 수 있는 가장 큰 값으로 초기화 해주었지만 max가 적을수록 메모리낭비가 생기기 때문에 이처럼 해준다.

 

dfs로직 : 매개변수로 depth, sum을 받는다. depth는 현재의 인덱스 위치, sum은 현재까지 저장된 값이다. depth 가 N이 될 경우 초기에는 answer배열의 모든값이 0이기 때문에 answer[sum]을 임의의 값 1로 변경해준후 return 한다.

        dfs(depth + 1, sum + arr[depth]); // 현재까지 저장된 sum + 현재 인덱스의 arr 값
        dfs(depth + 1, sum); // 현재 인덱스의 arr 값을 추가하지않고 다음 인덱스 탐색

이 부분은 배열의 완전탐색을 하기위한 알고리즘이다. 인덱스를 증가시키면 배열을 탐색하다 현재 인덱스의 arr[depth] 더하고 다음 인덱스를 탐색할 경우에는 첫 번째를, 아니라면 두 번째를 실행해야한다. 모든 배열을 탐색해야하기 때문에 두 가지 모두 수행한다.

'코딩테스트' 카테고리의 다른 글

백준 1189번: 컴백홈[JAVA]  (0) 2024.03.28
백준 15686번: 치킨 배달[JAVA]  (0) 2024.03.28
백준 10819번: 차이를 최대로[JAVA]  (0) 2024.03.21
백준 1260번: DFS와 BFS[JAVA]  (0) 2024.03.20
백준 1068번: 트리[JAVA]  (1) 2024.03.15

+ Recent posts