14. [Java] 컬렉션 (Collection) 1 / 20230501

2023. 5. 2. 21:44

🧑🏻‍💻 TIL(Today I Learned)


🧑🏻‍💻 Enum, 제네릭, 예외 처리, 컬렉션 프레임워크

 

1. 열거형(Enum; enumerated type)

➡️ 관련 상수들을 같이 묶어 놓은 것, Java는 타입에 안전한 열거 제공 

➡️ 열거형은 값과 타입을 모두 체크함 

➡️ 기존의 상수들을 정의하던 방법(public static final, interface)들은 상수명이 중복되거나 타입안정성이 떨어지거나 코드가 너무 길어져 가독성이 떨어진다거나 하는 단점들이 존재함 또한 switch 문에 활용도 할 수 없음 
👉🏻 이런 문제들을 효과적으로 해결하기 위해서 만들어진 것이 "enum"

📍타입 안정성이란? 
: 프로그램이 의도한 대로 타입이 사용되는 것을 보장하는 것 

📍switch 문은 몇 가지 제한된 데이터 타입만 사용 가능 
: byte, short, char, int, enum, String, Character, Byte, Short, Integer
enum Seasons { SPRING, SUMMER, FALL, WINTER }
enum Frameworks { DJANGO, SPRING, NEST, EXPRESS }
  • 이런 식으로 코드를 작성하면 단점들을 해결할 수 있고 코드를 단순하고  가독성이 좋게 만들 수 있음 또한 switch 문에서 사용 가능 

public static final 사용하여 정의한 상수가 switch 문에서 작동하는 모습
출력 결과

  • 호환되지 않는 타입 : 정수로 타입 변환을 할 수 없다고 알려줌 
  • Seasons는 사용자 정의 타입이기 때문에 int형으로 변환할 수 없음 

enum 사용하여 정의한 상수가 switch 문에서 작동하는 모습

  • 출력 결과 : 봄
  • enum을 사용하면 좀 더 간결하고 편리하게 상수 사용가능 
    → 중복을 피하고 타입에 대한 안정성 보장
  • switch 문에서 사용 가능 

 

🔎 열거형의 사용 

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

enum Seasons { 
    SPRING, //정수값 0 할당
    SUMMER,  //정수값 1 할당
    FALL, //정수값 2 할당
    WINTER //정수값 3 할당
}

// 관례적으로 대문자 작성 
// 각각의 열거상수들은 객체
// 상수들에는 따로 값을 지정해주지 않아도 자동적으로 0부터 시작하는 정수값 할당되어 각각 상수 가리킴

 

🔎 사용할 수 있는 메서드 

리턴 타입 메서드(매개변수) 설명
String name() 열거 객체가 가지고 있는 문자열 리턴, 리턴되는 문자열은 열거타입을 정의할 때 사용한 상수 이름과 동일함 
int ordinal() 열거 객체의 순번(0부터 시작)을 리턴
int compareTo(비교값) 주어진 매개값과 비교해서 순번 차이 리턴
열거 타입 valueOf(String name) 주어진 문자열의 열거 객체 리턴
열거 타입 values() 모든 열거 객체들을 배열로 리턴 

 

 

2. 제네릭(Generic)

➡️ 타입을 구체적으로 지정한 것이 아니라 추후에 지정할 수 있도록 일반화해 두는 것

➡️ 작성한 클래스 또는 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해 둔 것 

만약 만든 클래스와 똑같은 기능을 하면서 인스턴스 변수에 다른 타입의 데이터를 저장할 수 있게 하려면 각 타입별로 같은 내용의 클래스를 만들어야 함 
👉🏻 하지만 '제네릭(Generic)'을 사용하면 그런 번거로움을 줄일 수 있음
class Basket {
    private String item;

    Basket(String item) {
        this.item = item;
    }

    public String getItem() {
        return item;
    }

    public void setItem(String item) {
        this.item = item;
    }
}

// 일반 코드

class Basket<T> {
    private T item;

    public Basket(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

// 제네릭 사용
// 제네릭 클래스 
// T 타입 매개변수 <T> 이런 식으로 클래스 이름 옆에 작성하여 클래스 내부에서 
// 사용할 타입 매개변수 선언 가능 --> 임의의 타입
// 매개변수가 여러개라면 class Basket<K, V>
// T -> type K -> key V -> value R -> result

📍 제네릭을 사용하면 Basket 클래스만으로 모든 타입의 데이터를 저장할 수 있는 인스턴스 만들 수 있음 

Basket<String> basket1 = new Basket<String>("바구니");
//“Basket 클래스 내의 T를 String으로 바꿔라.”

Basket<Integer> basket2 = new Basket<Integer>(1);
//“Basket 클래스 내의 T를 Integer으로 바꿔라.”

 

✔️ 주의할 점 

  • 클래스 변수에는 타입 매개변수를 사용할 수 없음
class Basket<T> {
	private T item1; // O 
	static  T item2; // X 
}

// 사용하게 된다면 클래스 변수의 타입이 인스턴스 별로 달라지게 됨 
// --> 클래스 변수를 통해 같은 변수를 공유하는 것이 아니게 됨
  • 제네릭 클래스에서 타입 매개변수를 넣어줄 때는 기본 자료형(primitive type)이 아닌 참조 자료형(reference type)만 사용 가능 
    → 제네릭 클래스에서는 '래퍼 클래스(Wrapper Class)' 사용
래퍼 클래스(Wrapper Class)란?
: 기본 타입의 데이터를 객체로 표현해야할 때 기본 타입을 객체로 다루기 위해서 사용하는 클래스 
기본 자료형 래퍼 클래스 
boolean Boolean
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character

 

 

🔎 제한된 제네릭

➡️ 일반적으로 위에서 정리했던 것처럼 만들면 타입 제한 없이 제네릭을 만들 수 있음

➡️ 하지만 아래와 같이 타입을 지정할 수 있도록 만들어줄 수도 있음

class Flower { ... }
class Rose extends Flower { ... }
class RosePasta { ... }

// 클래스를 인스턴스화 할 때 타입으로  Flower 클래스의 하위 클래스만 가능하도록 설정
class Basket<T extends Flower> { 
    private T item;
	
		...
}

class Main {
    public static void main(String[] args) {
    
        // 인스턴스화 
        Basket<Rose> roseBasket = new Basket<>();
        Basket<RosePasta> rosePastaBasket = new Basket<>(); // 에러
    }
}


------------------------------------------------------------------------
interface Plant { ... }
class Flower implements Plant { ... }
class Rose extends Flower implements Plant { ... }

// 특정 인터페이스를 구현한 클래스 타입으로만 지정할 수 있도록 제한 가능 
class Basket<T extends Plant> {
    private T item;
	
		...
}

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

        // 인스턴스화 
        Basket<Flower> flowerBasket = new Basket<>();
        Basket<Rose> roseBasket = new Basket<>();
    }
}

📍 아래와 같이 동시에 타입을 지정할 수도 있음, & 사용
      → 이러한 경우에는 클래스를 인터페이스를 앞에 위치시켜야 함

interface Plant { ... }
class Flower implements Plant { ... }
class Rose extends Flower implements Plant { ... }

class Basket<T extends Flower & Plant> { // 클래스 & 인터페이스
    private T item;
	
		...
}

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

        // 인스턴스화 
        Basket<Flower> flowerBasket = new Basket<>();
        Basket<Rose> roseBasket = new Basket<>();
    }
}

 

 

🔎 제네릭 메서드 

➡️ 클래스 전체를 제네릭으로 선언할 수도 있지만, 클래스 내부의 특정 메서드만 제네릭으로 선언할 수도 있음 

class Basket {
		...
		public <T> void add(T element) {
				...
		}
}
// 타입 매개 변수 선언은 반환 타입 앞에서 이루어지며 해당 메서드 내에서만 선언한 타입 매개 변수를 사용할 수 있음
class Basket<T> {                        // 1 : 여기에서 선언한 타입 매개 변수 T와 
		...
		public <T> void add(T element) { // 2 : 여기에서 선언한 타입 매개 변수 T는 서로 다른 것
				...
		}
}

Basket<String> basket = new Bakset<>(); // 위 예제의 1의 T가 String으로 지정 
basket.<Integer>add(10);                // 위 예제의 2의 T가 Integer로 지정
basket.add(10);                         // 타입 지정을 생략할 수도 있음


class Basket {
		...
		static <T> int setPrice(T element) {
				...
		}
}
  • 제네릭 메서드에서의 타입 매개 변수는 제네릭 클래스 타입 매개 변수와 별개의 것!
    → 지정되는 시점이 다르기 때문에 
    • 클래스명 앞에서 선언한 타입 매개 변수는 클래스가 인스턴스화 될 때 타입 지정
    • 제네릭 메서드의 타입 지정은 메서드가 호출될 때 이루어짐 
  • 클래스 타입 매개 변수와는 달리 메서드 타입 매개 변수는 static 메서드에서도 선언하여 사용가능 
  • 제네릭 메서드는 메서드가 호출될 시점에 제네릭 타입이 결정되기 때문에 제네릭 메서드를 정의하는 시점에서 실제 어떤 타입이 입력되는지 알 수 없음 
    → 그렇기 때문에 String 클래스의 메서드 length() 와 같은 메서드 사용 불가 
    → 하지만 최상위 Object 클래스의 메서드는 사용가능 (equals(), toString() 등)

 

 

🔎 와일드카드

➡️ 제네릭에서 와일드카드는 어떠한 타입으로든 대체될 수 있는 타입 파라미터 

➡️ 기호 ? 로 사용가능 

<? extends T>
<? super T>
  • <? extends T>
    : 와일드카드에 상한 제한을 두는 것, T와 T를 상속받는 하위클래스 타입만 타입 파라미터로 받을 수 있도록 지정 
  • <? super T>
    : 와일드카드에 하한 제한을 두는 것, T와  T의 상위클래스만 타입 파라미터로 받을 수 있도록 지정 
  • <?>
    : extends와  super 키워드와 조합하지 않은 와일드카드 
    <? extends Object> 와 같음
    모든 클래스 타입은  Object 클래스 상속을 받기 때문에 모든 클래스 타입을 타입 파라미터로 받을 수 있음

 

3. 예외 처리(Exception Handling)

➡️ 예기치 않게 발생하는 에러에 대응할 수 있는 코드를 미리 사전에 작성하여 프로그램의 비정상적인 종료를 방지하고 정상적인 실행 상태를 유지하기 위한 예외 처리 

컴파일 에러와 런타임 에러

  • 에러 발생 시점에 따라 아래와 같이 나눌 수 있음 
    • 컴파일 에러(Compile Error) :  컴파일할 때 발생하는 에러 
      → 주로 세미콜론 생략, 오탈자, 잘못된 자료형, 잘못된 포맷 등 문법적인 문제를 가리키는 syntax 오류로부터 발생(Syntax Error)
      → 상대적으로 발견하기도 쉽고 어렵지 않게 해결가능 
      →  컴파일러에 의해 발견됨 
    • 런타임 에러(Runtime Error) : 런타임 시에 발생하는 에러, 프로그램이 실행될 때 
      → 주로 개발자가 컴퓨터가 수행할 수 없는 특정한 작업을 요청할 때 발생 
      → 프로그램이 실행될 때 자바 가상 머신(JVM)에 의해 감지됨

 

🔎 에러와 예외 

➡️ 에러는 한 번 발생하면 복구하기 어려운 수준의 심각한 오류 
     (ex.  OutOfMemory(메모리 부족), StackOverFlowError 등)

➡️ 예외는 잘못된 사용 또는 코딩으로 인한 상대적으로 미약한 수준의 오류로서 코드 수정을 통해 수습가능한 오류 지칭

 

✔️ 예외 클래스 계층 상속도

➡️ Throwable 클래스로부터 확장, 모든 예외 최고 상위 클래스는 Exception

➡️ Exception 클래스는 일반 예외 클래스와 실행 예외 클래스로 나눌 수 있음\

  • 일반 예외 클래스(Exception)
    : 런타임 시 발생하는 Runtime Exception과 그 하위 클래스를 제외한 모든 Exception 클래스와 하위 클래스 가리킴 
      컴파일러가 코드 실행 전에 예외 처리 코드 여부를 검사한다고 하여 checked 예외라고 부르기도 함 
      주로 잘못된 클래스명이나 데이터 형식의 사용자 편의 실수로 발생하는 경우 많음 
  • 실행 예외 클래스 (Runtime Exception)
    : 런타임 시 발생하는 Runtime Exception과 그 하위 클래스
      컴파일러가 예외 처리 코드 여부를 검사하지 않는다는 의미에서 unchecked 예외라고 부르기도 함 
      개발자의 실수로 발생하는 경우가 많고 자바 문법 요소와 관련있음 
      클래스 간 형변환 오류, 벗어난 배열 범위, 값이 null인 참조변수 사용 등 

 

🔎 try - catch 문

try {
    // 예외가 발생할 가능성이 있는 코드를 삽입
} 
catch (ExceptionType1 e1) { // 여러 종류의 예외 처리 가능 , Exception 클래스 하나로 처리도 가능 --> 하지만 권장 x
    // ExceptionType1 유형의 예외 발생 시 실행할 코드
} 
catch (ExceptionType2 e2) {
    // ExceptionType2 유형의 예외 발생 시 실행할 코드
} 
finally {
    // finally 블록은 옵셔널
    // 예외 발생 여부와 상관없이 항상 실행
}

// 만약 작성한 코드가 예외 없이 정상적으로 실행되면 catch 블록은 실행되지 않고 finally 블록 실행
// 만약 catch 블록이 여러 개인 경우 일치하는 하나의 catch 블록만 실행되고 예외처리 코드가 종료되거나 finally 블록으로 넘어감
// 일치하는 블록을 차지 못할 때는 예외 처리되지 못함

 

🔎 예외전가

➡️ try-catch 문 외에 예외를 호출한 곳으로 다시 예외를 떠넘기기

➡️ 메서드 선언부 끝에 throws 키워드와 발생할 수 있는 예외들을 쉼표로 구분하여 나열하면 됨 

반환타입 메서드명(매개변수, ...) throws 예외클래스1, 예외클래스2, ... {
	...생략...
}

// 특정 메서드에서 모든 종류의 예외가 발생할 가능성이 있는 경우 
void ExampleMethod() throws Exception {
}

 


어제 올렸어야 되는데 진도 빼고 모르는 거 다시 보고 이해하느라 시간이 없었다. 그래도 다행히 오늘은 좀 복습하고 정리해서 올릴 시간이 생겼다. 내용이 워낙 많아서 두 개 분량으로 쪼개기로 했다. 정리를 하면서 다시 보니까 또 새로운 느낌이다.😂 그래도 다시 한 번 더 챙겨보게 되고 몰랐던 부분들 이해하기 어려웠던 부분들을 짚고 가니 좀 더 수월하긴 했다. 복습만이 살 길! 

BELATED ARTICLES

more