16Days) 컬렉션(1) - Enum, 제네릭, 예외 처리, 컬렉션 프레임워크

nacSeo (낙서)·2022년 11월 10일
0

◉ 학습목표

1. Enum의 등장 배경을 이해하고, 정의 및 메서드를 활용할 수 있다.
2. 제네릭 관련 내용들을 이해하고, 활용할 수 있다.
3. 에러와 예외에 대해 이해하고, try-catch문을 사용하여 예외 처리를 할 수 있다.
4. 컬렉션 프레임워크의 인터페이스들을 이해하고, 각각의 메서드를 통해 상황별로 컬렉션 클래스를 활용할 수 있다.
  1. 열거형 (Enum)

⦿ 학습내용

☞ 열거형 (Enum)

✔︎ 열거형 : 서로 연관된 상수들의 집합
✔︎ 상수 : 변하지 않는 값, final 키워드 사용
✔︎ 열거형의 장점
✓ 여러 상수들을 보다 편리하게 선언 및 관리 가능
✓ 상수명의 중복을 피하고, 타입에 대한 안정성 보장
✓ 비교적 훨씬 더 간결하고 가독성 좋은 코드로 표현 가능
switch 문에서도 작동 가능
✔︎ 열거형 정의

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

✓ 상수명은 관례적으로 대문자로 표현
ex) SPRING, SUMMER, FALL, WINTER
✓ 각 상수들에 값을 따로 지정하지 않아도 자동적으로 0부터 시작하는 정수값이 할당
✔︎ 열거형에서 사용 가능한 메서드

  1. 제네릭 (Generic)

⦿ 학습내용

☞ 제네릭 (Generic)

✔︎ 제네릭
✓ 클래스나 메서드의 코드를 작성할 때, 타입을 구체적으로 지정하는 것이 아니라, 추후에 지정할 수 있도록 일반화해두는 것
✓ 작성 클래스 또는 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해둔 것

☞ 래퍼 클래스 (Wrapper class)

✔︎ 래퍼 클래스 : 기본 타입에 해당하는 데이터를 객체로 포장해주는 클래스
✔︎ 박싱(Boxing)과 언박싱(UnBoxing)
✓ 박싱 : 기본 타입의 데이터를 래퍼 클래스의 인스턴스로 변환하는 과정
✓ 언박싱 : 래퍼 클래스의 인스턴스에 저장된 값을 다시 기본 타입의 데이터로 꺼내는 과정

☞ 제네릭 클래스

✔︎ 제네릭 클래스 : 제네릭이 사용된 클래스
✔︎ 타입 매개변수 : 꺽쇠(<>)안에 넣어 클래스 이름 옆에 작성
✔︎ 자주 사용되는 타입 매개변수
T : Type
K : Key, V : Value
E : Element
N : Number
R : Rerult
✔︎ 제네릭 클래스 정의 시 주의할 점 🔥
✓ 클래스 변수에는 타입 매개변수 사용 ❌
✓ 클래스 변수의 타입이 인스턴스 별로 달라지기 때문

class Basket<T> {
	private T item1;	// O
    static T item2;		// X

✔︎ 타입 매개변수에 치환될 타입으로 기본 타입 지정 불가, 래퍼 클래스 활용

Basket<String> basket1 = new Basket<>("Hello");
Basket<Integer> basket2 = new Basket<>(10);
Basket<Double> basket3 = new Basket<>(1.5);

✔︎ 제네릭 클래스에서도 다형성 적용 가능 (extends 활용)

☞ 제한된 제네릭 클래스

✔︎ 특정 클래스를 상속받은 클래스만 타입으로 지정할 수 있도록 제한

class Fruit { ... }
class Apple extends Fruit { ... }
class ApplePie { ... }

class Basket<T extends Fruit> {
	private T item;
    
    ...
}

public static void main(String[] args) {
	
    // 인스턴스화
    Basket<Apple> appleBasket = new Basket<>();
    Basket<ApplePie> applePieBasket = new Basket<>();	// 에러

✔︎ 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한

interface Food { ... }
class Fruit implements Food { ... }
class Apple extends Fruit implements Food { ... }
// 이미 Food를 implelements하고 있는 Fruit을 extends 하고 있기 때문에 뒤에 implements Food를 없애도 가능

class Basket<T extends Food> {	// 인터페이스 Food를 implements하고 있는 클래스들은 인스턴스 가능
	private T item;
    
    ...
}

public static void main(String[] args) {

	// 인스턴스화
    Basket<Fruit> fruitBasket = new Basket<>();
    Basket<Apple> appleBasket = new Basket<>();
}

✔︎ 특정 클래스를 상속받으면서 동시에 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한 : & 사용 (단, 클래스를 인터페이스보다 앞에 위치)

interface Food { ... }
class Fruit implements Food { ... }
class Apple extends Fruit implements Food { ... }

class Basket<T extends Fruit & Food> {	// 클래스 & 인터페이스 순으로 사용
	private T item;
    
    ...
}

public static void main(String[] args) {

	// 인스턴스화
    Basket<Fruit> fruitBasket = new Basket<>();
    Basket<Apple> appleBasket = new Basket<>();
}

☞ 제네릭 메서드

✔︎ 제네릭 메서드 : 클래스 내부 특정 메서드에 제네릭으로 선언하는 것
✔︎ 제네릭 메서드의 타입 매개변수 ≠ 제네릭 클래스의 타입 매개변수 (별개 ‼️)
✓ 제네릭 클래스의 타입 매개변수 : 클래스가 인스턴스화할 때 타입 지정
✓ 제네릭 메서드의 타입 매개변수 : 메서드가 호출될 때 지정

class Basket<T> {	// (1) 클래스의 타입 매개변수
	...
    public <T> void add(T element) {	// (2) 메서드의 타입 매개변수
    	...								// (1), (2)의 T는 별개!!
    }
}

Basket<String> basket = new Basket<>();		// (1)의 타입 매개변수 선언
basket.<Integer>add(10);					// (2)의 타입 매개변수 선언
basket.add(10);								// (2)의 <Integer> 선언 생략 가능

✔︎ 제네릭 클래스의 타입 매개변수와 달리 메서드의 타입 매개변수는 static 메서드에서도 선언 가능

class Basket {
	...
    static <T> int setPrice(T element) {	// static 가능
    	...
    }
}

☞ 와일드카드

✔︎ 와일드카드 : 어떤 타입으로든 대체될 수 있는 타입 파라미터
✔︎ ? 로 사용
✔︎ <? extends T> : 상한 제한, TT상속받는 하위 클래스 타입만 타입 파라미터로 받음
✔︎ <? super T> : 하한 제한, T T상위 클래스 타입만 타입 파라미터로 받음
✔︎ <?> = <? extends Object> : 모든 클래스 타입을 타입 파라미터로 받을 수 있음

  1. 예외 처리 (Exception Handling)

⦿ 학습내용

☞ 예외 처리 (Exception Handling)

✔︎ 예외 처리 : 잠재적으로 발생할 수 있는 비정상 종료나 오류를 대비하여 정상 실행을 유지할 수 있도록 처리하는 코드 작성 과정
✔︎ 에러 발생 원인
외부적 요인 : 하드웨어 문제, 네트워크 문제, 사용자 조작 오류 등
내부적 요인 : 개발자의 코드 작성 에러
✔︎ 발생 시점에 따른 에러 구분
① 컴파일 에러 (Compile Time Error)
② 런타임 에러 (Run Time Error)
③ 논리적 에러 (Logical Error)

☞ 컴파일 에러 (Compile Time Error)

✔︎ 컴파일 에러 (신택스 에러(Syntax Errors)) : 컴파일 할 때 발생하는 에러
✔︎ 주로 문법 요소와 신택스에 관련된 에러
✔︎ 자바 컴파일러가 오류를 감지하여, 사용자에게 알려주기 때문에 상대적으로 쉽게 발견 및 수정 가능

☞ 런타임 에러 (Run Time Error)

✔︎ 런타임 에러 : 런타임 시에 발생하는 에러, 프로그램이 실행될 때 발생하는 에러
✔︎ 주로 컴퓨터가 수행할 수 없는 특정한 작업을 요청할 때 발생
✔︎ 자바 가상 머신(JVM)에 의해 감지

☞ 에러와 예외

✔︎ 오류를 크게 에러(Error)예외(Exception)으로 구분
✔︎ 에러 : 한번 발생하면 복구하기 어려운 수준의 심각한 오류
ex) 메모리 부족(OutOfMemoryError), 스택 오버플로우(StackOverflowError)
✔︎ 예외 : 잘못된 사용 또는 코딩으로 인한 상대적으로 미약한 수준의 오류, 코드 수정 등을 통해 수습 가능한 오류

☞ 예외 클래스의 상속 계층도


✔︎ Exception 클래스 : 모든 예외의 최상위 클래스
✔︎ 일반 예외 클래스실행 예외 클래스로 나뉨
✔︎ 일반 예외 클래스 (Exception)
✓ RuntimeException 클래스와 그 하위 클래스를 제외한 모든 Exception 클래스와 그 하위 클래스
✓ 컴파일러가 코드 실행 전에 예외 처리 코드 여부를 검사 (checked 예외라고도 불림)
✓ 주로 잘못된 클래스명(ClassNotFoundException)이나 데이터 형식(DataFormatException) 등 사용자편의 실수로 인해 발생
✔︎ 실행 예외 클래스 (Runtime Exception)
✓ 런타임 시 발생하는 RuntimeException 클래스와 그 하위 클래스
✓ 컴파일러가 예외 처리 코드 여부를 검사 ❌ (unchecked 예외라고도 불림)
✓ 주로 개발자의 실수로 발생, 자바 문법 요소와 관련
✓ 클래스 간 형변환 오류(ClassCastException), 벗어난 배열 범위 지정(ArrayIndexOutOfBoundsException), 값이 null인 참조변수 사용(NullPointerException) 등

☞ try - catch 문

try {
	// 예외가 발생할 수 있는 코드 삽입
}
catch (ExceptionType1 e1) {
	// ExceptionType1 유형의 예외 발생 시 실행할 코드
}
catch (ExceptionType2 e2) {
	// ExceptionType2 유형의 예외 발생 시 실행할 코드
}
finally {
	// finally 블록은 옵셔널
    // 예외 발생 여부와 관계없이 항상 실행
}

☞ 예외 전가

✔︎ 메서드 선언부 끝에 throws 키워드와 발생할 수 있는 예외클래스들 나열

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

✔︎ 예외를 의도적으로 발생시키기 : throw 키워드 사용

public static void main(String[] args) {
	try {
    	Exception intendedException = new Exception("의도된 예외");
        throw intendedException;
    }
    catch (Exception e) {
    	System.out.println("성공");
    }
}
  1. 컬렉션 프레임워크 (Collection Framework)

◉ 학습내용

☞ 컬렉션 프레임워크 (Collection Framework)

✔︎ 컬렉션 : 여러 데이터들의 집합
✔︎ 컬렉션 프레임워크
✓ 컬렉션을 다루는 데에 있어 편리한 메서드들을 미리 정의해놓은 것
✓ 특정 자료 구조에 데이터를 추가, 삭제, 수정, 검색하는 등의 동작을 수행하는 편리한 메서드들을 제공
✔︎ 컬렉션 프레임워크의 구조
✔︎ 요약
ListSet은 공통점이 많아 Collection이라는 인터페이스로 묶음
✔︎ Collection인터페이스 : List와 Set의 공통점이 추출되어 추상화한 것
✓ 사용 가능한 메서드들

☞ List<E>

✔︎ List 인터페이스
✓ 배열과 같이 객체를 일렬로 늘어놓은 구조
✓ 객체를 저장하면 자동으로 인덱스가 부여
✓ 인덱스로 객체를 검색, 추가, 삭제할 수 있는 등 여러 기능 제공
✓ 데이터 저장 순서 유지 ⭕️, 중복 저장 ⭕️
ArrayList, LinkedList, Vector, Stack 등이 List 인터페이스 구현
✓ 사용 가능한 메서드들 (※ Collection 인터페이스의 메서드들 또한 상속받아 사용 가능)
✔︎ ArrayList
✓ 크기를 확장시킬 때 자동으로 저장용량이 늘어남
✓ 데이터가 연속적으로 존재
✓ 순차적일 때 효율적

ArrayList<타입_매개변수> 객체명 = new ArrayList<타입_매개변수>(초기_저장_용량);

ArrayList<String> container1 = new ArrayList<String>();
// String타입의 객체를 저장하는 ArrayList 생성
// 초기 용량이 인자로 전달되지 않으면 기본적으로 10으로 지정

ArrayList<String> container2 = new ArrayList<String>(10);
// String타입의 객체를 저장하는 ArrayList 생성
// 초기 용량이 30으로 지정

✓ ArrayList에 객체를 추가하면 인덱스 0부터 차례대로 저장
✓ 특정 인덱스의 객체를 제거하면, 바로 뒤 인덱스부터 마지막 인덱스까지 앞으로 1씩 당겨짐
∴ 빈번한 객체 삭제와 삽입이 일어나는 곳은 LinkedList에 비해 부적합
✔︎ LinkedList
✓ 데이터를 효과적으로 추가, 삭제, 변경하기 위해 사용
✓ 데이터가 불연속적으로 존재, 서로 연결(link)되어 있음
✓ 각 요소(node)들은 자신과 연결된 이전 요소 및 다음 요소의 주소값과 데이터로 구성
✓ 데이터를 추가∙삭제 시, 추가∙삭제하는 요소의 이전의 요소가 추가∙삭제하는 요소의 다음의 요소를 참조하도록 변경하는 방식
✓ 배열처럼 복사할 필요가 없기 때문에 처리 속도가 훨씬 빠름
✔︎ ArrayList VS LinkedList
✓ ArrayList 장점
① 데이터를 순차적으로 추가하거나 삭제하는 경우
② 데이터를 읽어들이는 경우 (검색)
✓ ArrayList 단점
① 중간에 데이터를 추가하거나, 중간에 위치하는 데이터를 삭제하는 경우
✓ LinkedList 장점
① 중간에 위치하는 데이터를 추가하거나 삭제하는 경우
✓ LinkedList 단점
① 순차적으로 추가∙삭제하거나, 검색할 때 ArrayList보다 느림
∴ 데이터의 잦은 변경이 예상될 때 → LinkedList, 데이터의 개수가 변하지 않을 때 → ArrayList

☞ Iterator

✔︎ Iterator : 반복자라는 의미, 컬렉션에 저장된 요소들을 순차적으로 읽어오는 역할
✔︎ Collection 인터페이스에 정의된 iterator()를 호출하면, Iterator 타입의 인스턴스가 반환
∴ Collection 인터페이스에서 상속받은 List와 Set 인터페이스를 구현한 클래스들은 iterator() 메서드 사용 가능
✔︎ 사용 가능한 메서드들**

☞ Set<E>

✔︎ 데이터 저장 순서 유지 ❌, 중복 저장 ❌
✔︎ HashSet, TreeSet 등이 Set 인터페이스 구현
✔︎ 사용 가능한 메서드들
✔︎ HashSet
✓ Set의 특성대로 데이터 저장 순서 유지 ❌, 중복 저장 ❌
✓ 중복 값 확인 방법
add(Object o)를 통해 객체 저장
② 저장하고자 하는 객체의 해시코드를 hashcode() 메서드를 통해 얻어 냄
③ Set이 저장하고 있는 모든 해시코드를 hashcode() 메서드를 통해 얻어 냄
④ 저장하고자 하는 객체의 해시코드와 Set에 이미 저장되어 있던 해시코드를 비교해 같은 해시코드가 있는 지 검사
④-ⓐ 같은 해시코드 존재 시, ⑤번으로 넘어감
④-ⓑ 같은 해시코드 존재하지 않을 시, Set에 객체 추가 & add(Object o) 메서드가 true 리턴
equals() 메서드를 통해 객체 비교
⑤-ⓐ true 리턴 시, 중복 객체로 간주되어 Set에 객체 추가 ❌ & add(Object o) 메서드가 false 리턴
⑤-ⓑ false 리턴 시, Set에 객체 추가 & add(Object o) 메서드가 true 리턴
✔︎ TreeSet
✓ 이진 탐색 트리 형태로 데이터 저장
✓ Set의 특성대로 데이터 저장 순서 유지 ❌, 중복 저장 ❌
✓ 이진 탐색 트리 : 하나의 부모 노드가 최대 2개의 자식 노드와 연결된 이진 트리(Binary Tree)의 일종, 정렬과 검색에 특화
✓ 이진 탐색 트리의 특성
① 루트 : 최상위 노드
② 모든 왼쪽 자식 노드의 값이 부모 노드의 값보다 작고, 모든 오른 자식 노드의 값이 부모 노드보다 큰 값을 가짐
✓ 사용 가능한 메서드들
🌟 highersubSet 계열 메서드 이해하기 힘들었음❗️ 주의 깊게 볼 것

☞ Map<K, V>

✔︎ 키(Key)와 값(Value)의 쌍으로 데이터 저장
✔︎ Entry 객체 = Key 객체 + Value 객체로 저장
✔︎ 데이터 저장 순서 유지 ❌
✔︎ 중복 저장 ❌ (값을 식별하기 위해 사용되기 때문)
✔︎ 중복 저장 ⭕️
✔︎ HashMap, HashTable, TreeMap, Properties 등
✔︎ 사용 가능한 메서드들
✔︎ HashMap
✓ 해시 함수를 통해 '키'와 '값'이 저장되는 위치를 결정하므로, 사용자도 알 수 없고, 순서와 위치 또한 관계 ❌
✓ 해싱(Hashing)을 사용하기 때문에 많은 양의 데이터를 검색하는 데에 있어 뛰어난 성능을 보임
✓ HashMap의 개별 요소인 Entry 객체는 Map 인터페이스의 내부 인터페이스인 Entry 인터페이스를 구현
✓ Map.Entry 인터페이스의 메서드들
✓ HashMap 생성

HashMap<String, Integer> hashmap = new HashMap<>();

✓ Map은 키와 값을 쌍으로 저장하기 때문에 iterator() 메서드 직접 호출 불가
keySet 또는 entrySet 메서드를 이용해 Set 형태로 반환된 컬렉션에 iterator() 메서드를 호출하여 반복자를 만든 후, 반복자를 통해 순회 가능

◉ 느낀 점

☞ 객체지향 프로그래밍보다 어려운 내용은 아니였지만, 마지막 HashMap에서 entrySet을 통해 반복자를 사용하는 부분은 좀 복잡했다 😵‍💫 또한, 내용이 무척이나 많아서 하루만에 모든 것을 습득하기엔 부족한 시간이었던 것 같다. 많은 것을 머릿 속에 집어넣은 하루라 잘 되새겨봐야겠다! (복습,, 또 복습,,!)

배열을 배우면서 데이터가 추가시키기 위해 크기를 확장시키고 싶을 때마다 새로운 배열을 생성해서, 기존 배열을 복사해서 다시 넣고.. 많이 복잡한 과정이었는데 이번 이 컬렉션을 배우면서 많이 편리해질 수 있을 것 같다 :)

또한, 저장 순서가 유지되야 하는지, 중복 저장이 필요한지, 키-값 쌍 구조로 되어 있는지, 빈번히 삽입/삭제되는지, 정렬이 필요한지 등에 따라 어떤 컬렉션 클래스를 사용해야 하는지 잘 판단하고 선택하는 재미가 있는 것 같다! 처음에는 뭘 사용해야할 지 감이 안오겠지만 자꾸 사용하다보면 어느 순간 척척 뽑아내지 않을까? ☺️

오늘 배운 내용들을 잘 이해해서 내일 페어와 함께 실습을 하며 잘 익혀나가도록 하겠다❗️

◉ 내일의 키워드

・ 컬렉션 연습문제 풀이 with Pair
profile
백엔드 개발자 김창하입니다 🙇‍♂️

0개의 댓글