Section1 - 컬렉션(Collection)

열거형(Enum)

서로 연관된 상수들의 집합

  • 상수들을 편리하게 선언, 관리할 때 사용
  • 몇 가지로 한정된 변하지 않는 데이터를 다루는데 사용
  • 상수명의 중복을 피하고, 타입에 대한 안정성을 보장
  • 같은 효과를 낼 수 있는 다른 코드에 비해
    • 훨씬 더 간결하고 가독성이 좋음
    • switch문에서도 작동이 가능
enum 열거형이름 { 상수명1, 상수명2, 상수명3, ...}
enum Seasons { SPRING, SUMMER, FALL, WINTER }
enum Frameworks { DJANGO, SPRING, NEST, EXPRESS }

열거형의 사용

  • 상수는 대소문자로 모두 작성이 가능하지만 관례적으로 대문자로 작성

  • 각각의 상수들에는 따로 값을 지정해주지 않아도 자동적으로 0부터 시작하는 정수값이 할당되어 각각의 상수를 가리키게 됨

선언 -> 열거형이름.상수명으로 호출
(static변수를 참조하는 것과 동일)

enum Level {
  LOW, // 0
  MEDIUM, // 1
  HIGH // 2
}

public class Main {
  public static void main(String[] args) {
    Level level = Level.MEDIUM;

    switch(level) {
      case LOW:
        System.out.println("낮은 레벨");
        break;
      case MEDIUM:
         System.out.println("중간 레벨");
        break;
      case HIGH:
        System.out.println("높은 레벨");
        break;
    }
  }
}

//출력값
중간 레벨

아래 메서드들은 모든 열거형의 조상인 java.lang.Enum 에 정의되어있는 것으로, 클래스에서 최상위 클래스 Object에 정의된 메서드들을 사용할 수 있었던 것과 동일하다

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

enum Level {
  LOW, // 0
  MEDIUM, // 1
  HIGH // 2
}

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

        Level[] allLevels = Level.values();
        for(Level x : allLevels) {
            System.out.printf("%s=%d%n", x.name(), x.ordinal());
        }

        Level findLevel = Level.valueOf("LOW");
        System.out.println(findLevel); // LOW
        System.out.println(Level.LOW == Level.valueOf("LOW")); // true
    }
}

//출력값
LOW=0
MEDIUM=1
HIGH=2
LOW
true

제네릭(Generic)

클래스나 메서드의 코드를 작성할 때, 타입을 구체적으로 지정하는 것이 아니라, 추후에 지정할 수 있도록 일반화해두는 것을 의미

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

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;
    }
}
Basket<String> basket1 = new Basket<String>("기타줄");
Basket<Boolean> basket3 = new Basket<Boolean>(true);
Basket<Double> basket4 = new Basket<Double>(3.14);

제네릭의 필요성

기존의 방식으로는 다양한 타입의 데이터를 저장하기 위해 각 타입별로 별도의 클래스를 만들어야 했다. 하지만 제네릭을 사용하면 단 하나의 Basket 클래스만으로 모든 타입의 데이터를 저장할 수 있는 인스턴스를 만들 수 있다. (코드가 단순해짐)


제네릭 클래스

제네릭이 사용된 클래스 (위 예시 같은거)

타입 매개변수를 여러 개 사용해야 한다면, 아래와 같이 선언하면 된다.

class Basket<K, V> { ... }

타입 매개변수는 임의의 문자로 지정 가능 (T,K,V 아니여도 됨)

제네릭 클래스를 정의할 때 주의할 점

클래스(static) 변수에는 타입 매개변수를 사용할 수 없다.

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>  basket2 = new Basket<>(3.14);

아래와 같이 다향성 적용도 가능하다.

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

class Basket<T> {
    private T item;

    public T getItem() {
        return item;
    }

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

public static void main(String[] args) {
		Basket<Flower> flowerBasket = new Basket<>();
		flowerBasket.setItem(new Rose());      // 다형성 적용
		flowerBasket.setItem(new RosePasta()); // 에러
}

제한된 제네릭 클래스

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

바로 위 예시와 같이 Basket 클래스를 인스턴스화할 때 타입으로 Flower 클래스의 하위 클래스만 지정하도록 제한하거나 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한할 수 있다.

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<>();
    }
}
class Basket<T extends Flower & Plant> { // (1)
    private T item;
	
		...
}

특정 클래스를 상속받으면서 동시에 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있게도 할 수 있다.
(다만, 이러한 경우에는 클래스를 인터페이스보다 앞에 위치시켜야 한다. )


제네릭 메서드

클래스 내부의 특정 메서드만 제네릭으로 선언할 수 있다.

class Basket {
		...
		public <T> void add(T element) {
				...
		}
}
  • 제네릭 메서드의 타입 매개변수는 제네릭 클래스의 타입 매개변수와 별개의 것이다.
class Basket<T> {                        // 1 : 여기에서 선언한 타입 매개변수 T와 
		...
		public <T> void add(T element) { // 2 : 여기에서 선언한 타입 매개변수 T는 서로 다른 것이다.
				...
		}
}

-> 제네릭 클래스의 타입 매개변수는 클래스가 인스턴스화될 때 타입이 지정 됨.

-> 제네릭 메서드의 타입 매개변수는 메서드가 호출될 때 타입이 지정 됨.

  • 클래스 타입 매개변수와 달리 메서드 타입 매개변수는 static 메서드에서도 선언하여 사용할 수 있다.

  • length()와 같은 String 클래스의 메서드는 제네릭 메서드를 정의하는 시점에 사용할 수 없다.
    (제네릭 메서드를 정의하는 시점에서는 실제 어떤 타입이 입력 되는지 알 수 없기 때문)

    • but Object 클래스의 메서드는 사용가능 (equals(), toString() 등)

와일드카드

어떠한 타입으로든 대체될 수 있는 타입 파라미터를 의미

일반적으로 와일드카드는 extends와 super 키워드를 조합하여 사용한다.

<? extends T> // 상한 제한
<? super T> // 하한 제한

extends 및 super 키워드와 조합하지 않은 와일드카드(<?>)는 모든 클래스 타입을 타입 파라미터로 받을 수 있음을 의미한다.

class PhoneFunction {
    public static void call(User<? extends Phone> user) {
        System.out.println("-----------------------------");
        System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
        System.out.println("모든 Phone은 통화를 할 수 있다.");
    }

    public static void faceId(User<? extends IPhone> user) {
        System.out.println("-----------------------------");
        System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
        System.out.println("IPhone만 Face ID를 사용할 수 있다. ");
    }

    public static void samsungPay(User<? extends Galaxy> user) {
        System.out.println("-----------------------------");
        System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
        System.out.println("Galaxy만 삼성 페이를 사용할 수 있다. ");
    }

    public static void recordVoice(User<? super Galaxy> user) {
        System.out.println("-----------------------------");
        System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
        System.out.println("안드로이드 폰에서만 통화 녹음이 가능하다. ");
    }
}
public class Example {
    public static void main(String[] args) {
        PhoneFunction.call(new User<Phone>(new Phone()));
        PhoneFunction.call(new User<IPhone>(new IPhone()));
        PhoneFunction.call(new User<Galaxy>(new Galaxy()));
        PhoneFunction.call(new User<IPhone12Pro>(new IPhone12Pro()));
        PhoneFunction.call(new User<IPhoneXS>(new IPhoneXS()));
        PhoneFunction.call(new User<S22>(new S22()));
        PhoneFunction.call(new User<ZFlip3>(new ZFlip3()));

        System.out.println("\n######################################\n");

//        PhoneFunction.faceId(new User<Phone>(new Phone())); // X
        PhoneFunction.faceId(new User<IPhone>(new IPhone()));
        PhoneFunction.faceId(new User<IPhone12Pro>(new IPhone12Pro()));
        PhoneFunction.faceId(new User<IPhoneXS>(new IPhoneXS()));
//        PhoneFunction.faceId(new User<Galaxy>(new Galaxy())); // X
//        PhoneFunction.faceId(new User<S22>(new S22())); // X
//        PhoneFunction.faceId(new User<ZFlip3>(new ZFlip3())); // X

        System.out.println("\n######################################\n");

//        PhoneFunction.samsungPay(new User<Phone>(new Phone())); // X
//        PhoneFunction.samsungPay(new User<IPhone>(new IPhone())); // X
//        PhoneFunction.samsungPay(new User<IPhone12Pro>(new IPhone12Pro())); // X
//        PhoneFunction.samsungPay(new User<IPhoneXS>(new IPhoneXS())); // X
        PhoneFunction.samsungPay(new User<Galaxy>(new Galaxy()));
        PhoneFunction.samsungPay(new User<S22>(new S22()));
        PhoneFunction.samsungPay(new User<ZFlip3>(new ZFlip3()));

        System.out.println("\n######################################\n");

        PhoneFunction.recordVoice(new User<Phone>(new Phone()));
//        PhoneFunction.recordVoice(new User<IPhone>(new IPhone())); // X
//        PhoneFunction.recordVoice(new User<IPhone12Pro>(new IPhone12Pro())); // X
//        PhoneFunction.recordVoice(new User<IPhoneXS>(new IPhoneXS())); // X
        PhoneFunction.recordVoice(new User<Galaxy>(new Galaxy()));
//        PhoneFunction.recordVoice(new User<S22>(new S22())); // X
//        PhoneFunction.recordVoice(new User<ZFlip3>(new ZFlip3())); // X
    }
}

주석 처리한 부분은 오류(사용불가)가 나는 케이스다.



예외 처리(Exception Handling)

에러에 대응할 수 있는 코드를 미리 사전에 작성하여 프로그램의 비정상적인 종료를 방지하고, 정상적인 실행 상태를 유지하는 방법

컴파일 에러와 런타임 에러

컴파일 에러

“컴파일 할 때" 발생하는 에러
ex) 세미콜론 생략, 오탈자, 잘못된 자료형, 잘못된 포맷 등

런타임 에러

런타임(실행) 시에 발생하는 에러
컴퓨터가 수행할 수 없는 작업을 요청할 때 발생
ex) print(4/0);, 길이가3인 배열의 5인덱스 값을 출력하라 등

에러와 예외

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

예외 클래스의 상속 계층도


위 그림 출처

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

try - catch문

try - catch 블럭을 통해 예외 처리 구현이 가능하다.

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

catch 블럭이 여러 개인 경우, 일치하는 하나의 catch 블럭만이 실행되고 예외처리 코드가 종료되거나 finally 블럭으로 넘어간다.

만약에 일치하는 블럭을 찾지 못하는 경우에는 예외는 처리되지 못한다.


예외 전가

try-catch 문 외에 예외를 호출한 곳으로 다시 예외를 떠넘기는 방법

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

모든 종류의 예외가 발생할 가능성이 있는 경우 아래와 같이 작성

void ExampleMethod() throws Exception {
}

아래와 같이 throws 키워드를 사용하여 해당 예외를 발생한 메서드 안에서 처리하지 않고 메서드를 호출한 곳으로 다시 떠넘길 수 있다.

ublic class ThrowExceptionTest {

    public static void main(String[] args) {
        try {
            throwException();
        } catch (ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }
    }

    static void throwException() throws ClassNotFoundException, NullPointerException {
        Class.forName("java.lang.StringX");
    }
}

//출력값
java.lang.StringX

예외를 의도적으로 발생시키기

throws 키워드와 유사한 throw 키워드를 사용하면 의도적으로 예외를 발생시킬 수 있다

public class ExceptionTest {

    public static void main(String[] args) {
        try {
            Exception intendedException = new Exception("의도된 예외 만들기");
            throw intendedException;
        } catch (Exception e) {
            System.out.println("고의로 예외 발생시키기 성공!");
        }
    }
    
}

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

특정 자료 구조에 데이터를 추가하고, 삭제하고, 수정하고, 검색하는 등의 동작을 수행하는 편리한 메서드들을 미리 정의해놓은 것

컬렉션 프레임워크의 구조

그림 출처

List

  • 데이터의 순서가 유지되며, 중복 저장이 가능한 컬렉션을 구현하는 데에 사용

  • ArrayList, Vector, Stack, LinkedList 등

Set

  • 데이터의 순서가 유지되지 않으며, 중복 저장이 불가능한 컬렉션을 구현하는 데에 사용

  • HashSet, TreeSet 등

Map

  • 키(key)와 값(value)의 쌍으로 데이터를 저장하는 컬렉션을 구현하는 데에 사용

  • 데이터의 순서가 유지되지 않으며, 키는 값을 식별하기 위해 사용되므로 중복 저장이 불가능하지만, 값은 중복 저장이 가능하다.

  • HashMap, HashTable, TreeMap, Properties 등

List와 Set은 서로 공통점이 많아 위 그림과 같이 Collection이라는 인터페이스로 묶인다. 즉, 이 둘의 공통점이 추출되어 추상화된 것이 바로 Collection이라는 인터페이스다.


Collection 인터페이스

Collection 인터페이스의 메서드들

기능리턴 타입메소드설명
객체 추가booleanadd(Object o) /
addAll(Collection c)
주어진 객체 및 컬렉션의 객체들을 컬렉션에 추가
객체 검색booleancontains(Object o) / containsAll(Collection c)주어진 객체 및 컬렉션이 저장되어 있는지 여부를 리턴
Iteratoriterator()컬렉션의 iterator를 리턴
booleanequals(Object o)컬렉션이 동일한지 여부를 확인
booleanisEmpty()컬렉션이 비어있는지 여부를 확인
intsize()저장되어 있는 전체 객체 수를 리턴
객체 삭제voidclear()컬렉션에 저장된 모든 객체를 삭제
booleanremove(Object o) / removeAll(Collection c)주어진 객체 및 컬렉션을 삭제하고 성공 여부를 리턴
booleanretainAll(Collection c)주어진 컬렉션을 제외한 모든 객체를 컬렉션에서 삭제하고, 컬렉션에 변화가 있는지의 여부를 리턴
객체 변환Object[]toArray()컬렉션에 저장된 객체를 객체배열(Object [])로 반환
Object[]toArray(Object[] a)주어진 배열에 컬렉션의 객체를 저장해서 반환

List<E>

List(인터페이스)

  • 배열과 같이 객체를 일렬로 늘어놓은 구조
  • 객체를 인덱스로 관리 (객체를 저장하면 자동으로 인덱스가 부여)
  • 인덱스로 객체를 검색, 추가, 삭제할 수 있는 등의 여러 기능을 제공

List 인터페이스에서 공통적으로 사용 가능한 메서드

기능리턴 타입메서드설명
객체 추가voidadd(int index, Object element)주어진 인덱스에 객체를 추가
booleanaddAll(int index, Collection c)주어진 인덱스에 컬렉션을 추가
Objectset(int index, Object element)주어진 위치에 객체를 저장
객체 검색Objectget(int index)주어진 인덱스에 저장된 객체를 반환
intindexOf(Object o) / lastIndexOf(Object o)순방향 / 역방향으로 탐색하여 주어진 객체의 위치를 반환
ListIteratorlistIterator() / listIterator(int index)List의 객체를 탐색할 수 있는ListIterator 반환 / 주어진 index부터 탐색할 수 있는 ListIterator 반환
ListsubList(int fromIndex, int toIndex)fromIndex부터 toIndex에 있는 객체를 반환
객체 삭제Objectremove(int index)주어진 인덱스에 저장된 객체를 삭제하고 삭제된 객체를 반환
booleanremove(Object o)주어진 객체를 삭제
객체 정렬voidsort(Comparator c)주어진 비교자(comparator)로 List를 정렬

ArrayList (List 인터페이스를 구현한 클래스)

  • 객체를 추가하면 객체가 인덱스로 관리된다. (배열과 유사)
  • 저장 용량을 초과하여 객체들이 추가되면, 자동으로 저장용량이 늘어남
  • 데이터가 연속적으로 존재(데이터의 순서를 유지)
ArrayList<타입 매개변수> 객체명 = new ArrayList<타입 매개변수>(초기 저장 용량);

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

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

ArrayList<Integer> container3 = new ArrayList<>(container1); 
// 다른 Collection값으로 초기화

ArrayList<Integer> container4 = new ArrayList<>(Arrays.asList(배열명); 
// Arrays.asList()
// 배열의 요소와 같은 객체로 구성 된 ArrayList 만들기

ArrayList에 String 객체를 추가, 검색, 삭제해보았다.

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

		// ArrayList를 생성하여 list에 할당
		ArrayList<String> list = new ArrayList<String>();

		// String 타입의 데이터를 ArrayList에 추가
		list.add("Java");
		list.add("egg");
		list.add("tree");

		// 저장된 총 객체 수 얻기
		int size = list.size(); 

		// 0번 인덱스의 객체 얻기
		String skill = list.get(0);

		// 저장된 총 객체 수 만큼 조회
		for(int i = 0; i < list.size(); i++){
			String str = list.get(i);
			System.out.println(i + ":" + str);
		}

		// for-each문으로 순회 
		for (String str: list) {
			System.out.println(str);
		}		

		// 0번 인덱스 객체 삭제
		list.remove(0);
        
        //삭제 후 list
        for (String str: list) {
            System.out.println( "AfterDeleteList :"+str);
        }
	}
}

//출력
0:Java
1:egg
2:tree
Java
egg
tree
AfterDeleteList :egg
AfterDeleteList :tree

공식문서에서 다양한 메서드를 찾을 수 있다. 암기x 필요할 때 찾아 쓰면서 익히자.


LinkedList

  • 데이터를 효율적으로 추가, 삭제, 변경하기 위해 사용
  • 데이터가 불연속적으로 존재하며, 이 데이터는 서로 연결(link)되어 있다.
  • 배열보다 처리 속도가 빠르다.

그림 출처

LinkedList의 각 요소(node)들은 자신과 연결된 이전 요소 및 다음 요소의 주소값과 데이터로 구성되어 있다.

요소(데이터) 하나를 삭제하면, 삭제하고자 하는 요소의 이전 요소가 삭제하고자 하는 요소의 다음 요소를 참조하도록 변경 된다. (링크를 끊어주는 방식)

이 밑에 작성한거 다 날아가서 두번째 쓰는 중...하...

	public static void main(String[] args) {
		
		// Linked List를 생성하여 list에 할당
		LinkedList<String> list = new LinkedList<>();

		// String 타입의 데이터를 LinkedList에 추가
		list.add("Java");
		list.add("egg");
		list.add("tree");

		// 저장된 총 객체 수 얻기
		int size = list.size(); 

		// 0번 인덱스의 객체 얻기
		String skill = list.get(0);

		// 저장된 총 객체 수 만큼 조회
		for(int i = 0; i < list.size(); i++){
			String str = list.get(i);
			System.out.println(i + ":" + str);
		}

		// for-each문으로 순회
		for (String str: list) {
			System.out.println(str);
		}		

		// 0번 인덱스 객체 삭제
		list.remove(0);
        
        for (String str: list) {
			System.out.println("AD :" + str);
		}
	}
}

//출력
0:Java
1:egg
2:tree
Java
egg
tree
AD :egg
AD :tree

ArrayList예제와 결과는 같다.


ArrayList와 LinkedList 차이

ArrayList

  • 데이터를 순차적으로 관리(추가,삭제)하는 경우 빠르다
    • 데이터를 이동하지 않아도 되기 때문
  • 데이터를 읽는(검색)게 빠르다.
    • 인덱스를 통해 바로 데이터에 접근할 수 있기 떄문
  • 중간에서 데이터를 관리하는게 느리다.
    • 해당 데이터의 앞뒤로 밀어주거나 당겨줘야 하기 때문

LinkedList

  • 데이터 검색이 느리다.
    • 시작 인덱스에서부터 찾고자하는 데이터까지 순차적으로 각 노드에 접근해야 하기 때문
  • 중간에 위치하는 데이터를 관리하는 경우
    • 앞뒤로 Prev와 Next의 주소값만 변경하면 되기 떄문

결론적으로, 데이터의 잦은 변경이 예상된다면 LinkedList를, 데이터의 개수가 변하지 않는다면 ArrayList를 사용하는 것이 좋다.


Iterator

컬렉션에 저장된 요소들을 순차적으로 읽어오는 역할

Collection 인터페이스에는 Iterator 인터페이스를 구현한 클래스의 인스턴스를 반환하는 메서드인 iterator()가 정의되어져 있다.

  • Collection 인터페이스에 정의된 iterator()를 호출하면, Iterator 타입의 인스턴스가 반환한다.

Iterator 인터페이스에 정의된 메서드
(iterator()를 통해 만들어진 인스턴스는 아래의 메서드를 사용할 수 있다.)

메서드설명
hasNext()읽어올 객체가 남아 있으면 true를 리턴하고, 없으면 false를 리턴한다.
next()컬렉션에서 하나의 객체를 읽어온다. 이 때, next()를 호출하기 전에 hasNext()를 통해
읽어올 다음 요소가 있는지 먼저 확인해야 한다.
remove()next()를 통해 읽어온 객체를 삭제한다. next()를 호출한 다음에 remove()를 호출해야 한다.
ArrayList<String> list = ...;
Iterator<String> iterator = list.iterator();

while(iterator.hasNext()) {     // 읽어올 다음 객체가 있다면 
	String str = iterator.next(); // next()를 통해 다음 객체를 읽어옵니다. 
	...
}

위아래 코드가 같은거다.

ArrayList<String> list = ...;
for(String str : list) {
	...
}

remove도 적용해 보았다.

ArrayList<String> list = ...;
Iterator<String> iterator = list.iterator();

while(iterator.hasNext()){        // 다음 객체가 있다면
	String str = iterator.next();   // 객체를 읽어오고,
	if(str.equals("str과 같은 단어")){ // 조건에 부합한다면
		iterator.remove();            // 해당 객체를 컬렉션에서 제거합니다. 
	}
}

개인적으로 아직 까지는 ArrayList로 다 할 수 있는 기능들이라 크게 필요성이 느껴지진 않는다.

Set<E>

요소의 중복을 허용하지 않고, 저장 순서를 유지하지 않는 컬렉션

Set 인터페이스에 정의된 메서드

기능리턴 타입메서드설명
객체 추가booleanadd(Object o)주어진 객체를 추가하고, 성공하면 true를, 중복 객체면
false를 반환한다.
객체 검색booleancontains(Object o)주어진 객체가 Set에 존재하는지 확인한다.
booleanisEmpty()Set이 비어있는지 확인한다.
IteratorIterator()저장된 객체를 하나씩 읽어오는 반복자를 리턴한다.
intsize()저장되어 있는 전체 객체의 수를 리턴한다.
객체 삭제voidclear()Set에 저장되어져 있는 모든 객체를 삭제한다.
booleanremove(Object o)주어진 객체를 삭제한다.

HashSet

  • Set 인터페이스를 구현한 가장 대표적인 컬렉션 클래스
    • Set 인터페이스의 특성을 그대로 물려받으므로 중복된 값을 허용하지 않으며, 저장 순서를 유지하지 않는다.
    public static void main(String[] args) {
        HashSet<String > languages = new HashSet<String>();

        // HashSet에 객체 추가
        languages.add("Java");
        languages.add("Python");
        languages.add("Javascript");
        languages.add("C++");
        languages.add("Kotlin");
        languages.add("Ruby");
        languages.add("Java"); // 중복

        // 반복자 생성하여 it에 할당
        Iterator it = languages.iterator();

        // 반복자를 통해 HashSet을 순회하며 각 요소들을 출력
        while(it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

//출력
Java
C++
Javascript
Ruby
Python
Kotlin

“Java”를 두 번 추가했지만 한 번만 저장됨.


TreeSet

이진 탐색 트리 형태로 데이터를 저장한다.

  • 이진 탐색 트리(Binary Search Tree)

    • 하나의 부모 노드가 최대 두 개의 자식 노드와 연결되는 이진 트리(Binary Tree)의 일종으로, 정렬과 검색에 특화된 자료 구조
  • 최상위 노드를 ‘루트'라고 한다.

  • 이진 탐색 트리는 모든 왼쪽 자식의 값이 루트나 부모보다 작고, 모든 오른쪽 자식의 값이 루트나 부모보다 큰 값을 가지는 특징이 있다.

자료출처

위 그림의 각 노드들은 아래 Node 클래스를 인스턴스화한 인스턴스에 해당한다.

이러한 자료 구조를 구현하기 위한 의사 코드는 아래와 같다.

class Node {
	Object element; // 객체의 주소값을 저장하는 참조변수 입니다. 
	Node left;      // 왼쪽 자식 노드의 주소값을 저장하는 참조변수입니다.
	Node right;     // 오른쪽 자식 노드의 주소값을 저장하는 참조변수입니다. 
}

적용 예제

import java.util.TreeSet;

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

				// TreeSet 생성
        TreeSet<String> workers = new TreeSet<>();

				// TreeSet에 요소 추가
        workers.add("Lee Java");
        workers.add("Park Hacker");
        workers.add("Kim Coding");

        System.out.println(workers);
        System.out.println(workers.first());
        System.out.println(workers.last());
        System.out.println(workers.higher("Lee")); //입력값보다 큰 데이터중 최소값 출력 없으면 null
        System.out.println(workers.subSet("Kim", "Park")); // Kim부터 Part까지의 객체들을 반환 (Park는 미포함)
    }
}

//출력
[Kim Coding, Lee Java, Park Hacker]
Kim Coding
Park Hacker
Lee Java
[Kim Coding, Lee Java]

자동으로 사전 편찬 순에 따라 오름차순으로 정렬됐다.
자료 출처


Map

키(key)와 값(value)으로 구성된 객체를 저장하는 구조

  • 키(key)와 값(value)으로 구성된 객체 = Entry객체

  • 키는 중복 저장될 수 없지만, 값은 중복 저장이 가능하다

Map 인터페이스를 구현한 클래스에서 공통적으로 사용 가능한 메서드

(List는 인덱스를 기준으로 관리되는 반면, Map은 키(key)로 객체들을 관리하기 때문에 키를 매개값으로 갖는 메서드가 많다.)

기능리턴 타입메서드설명
객체 추가Objectput(Object key, Object value)주어진 키로 값을 저장합니다. 해당 키가 새로운
키일 경우null을 리턴하지만, 동일한 키가
있을 경우에는 기존의 값을대체하고 대체되기
이전의 값을 리턴한다.
객체 검색booleancontainsKey
(Object key)
주어진 키가 있으면 true, 없으면 false를 리턴한다.
booleancontainsValue
(Object value)
주어진 값이 있으면 true, 없으면 false를 리턴한다.
SetentrySet()키와 값의 쌍으로 구성된 모든 Map.Entry 객체를
Set에 담아서 리턴한다.
Objectget(Object key)주어진 키에 해당하는 값을 리턴한다.
booleanisEmpty()컬렉션이 비어 있는지 확인한다.
SetkeySet()모든 키를 Set 객체에 담아서 리턴한다.
intsize()저장된 Entry 객체의 총 갯수를 리턴한다.
Collectionvalues()저장된 모든 값을 Collection에 담아서 리턴한다.
객체 삭제voidclear()모든 Map.Entry(키와 값)을 삭제한다.
Objectremove(Object key)주어진 키와 일치하는 Map.Entry를 삭제하고 값을 리턴한다.

HashMap

  • Map 인터페이스를 구현한 대표적인 클래스
    • 키와 값으로 구성된 객체를 저장

Map.Entry 인터페이스 메서드

리턴 타입메서드설명
booleanequals(Object o)동일한 Entry 객체인지 비교합니다.
ObjectgetKey()Entry 객체의 Key 객체를 반환합니다.
ObjectgetValue()Entry 객체의 Value 객체를 반환합니다.
inthashCode()Entry 객체의 해시코드를 반환합니다.
ObjectsetValue(Object value)Entry 객체의 Value 객체를 인자로 전달한 value 객체로 바꿉니다.

HashMap을 생성시 키의 타입(String), 값의 타입(Integer)을 따로 지정해야 한다.

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

예시

import java.util.*;

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

	    // HashMap 생성
        HashMap<String, Integer> map = new HashMap<>();

        // Entry 객체 저장
        map.put("피카츄", 85);
        map.put("꼬부기", 95);
        map.put("야도란", 75);
        map.put("파이리", 65);
        map.put("피존투", 15);

        // 저장된 총 Entry 수 얻기
        System.out.println("총 entry 수: " + map.size());

        // 객체 찾기
        System.out.println("파이리 : " + map.get("파이리"));

        System.out.println("-".repeat(60));

        // key를 요소로 가지는 Set을 생성 -> 아래에서 순회하기 위해 필요
        Set<String> keySet = map.keySet();

        // keySet을 순회하면서 value를 읽기
        Iterator<String> keyIterator = keySet.iterator();
        while(keyIterator.hasNext()) {
            String key = keyIterator.next();
            Integer value = map.get(key);
            System.out.println(key + " : " + value);
        }

        System.out.println("-".repeat(60));

        // 객체 삭제
        map.remove("피존투");

        System.out.println("총 entry 수: " + map.size());

        System.out.println("-".repeat(60));

        // Entry 객체를 요소로 가지는 Set을 생성 -> 아래에서 순회하기 위해 필요
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();

        // entrySet을 순회하면서 value를 읽기
        Iterator<Map.Entry<String, Integer>> entryIterator = entrySet.iterator();
        while(entryIterator.hasNext()) {
            Map.Entry<String, Integer> entry = entryIterator.next();
            String key = entry.getKey(); // Map.Entry 인터페이스의 메서드
            Integer value = entry.getValue(); // Map.Entry 인터페이스의 메서드
            System.out.println(key + " : " + value);
        }

        // 객체 전체 삭제
        map.clear();
    }
}

//출력
총 entry 수: 5
파이리 : 65
------------------------------------------------------------
야도란 : 75
꼬부기 : 95
파이리 : 65
피카츄 : 85
피존투 : 15
------------------------------------------------------------
총 entry 수: 4
------------------------------------------------------------
야도란 : 75
꼬부기 : 95
파이리 : 65
피카츄 : 85

종료 코드 0()로 완료된 프로세스

Map은 키와 값을 쌍으로 저장하기 때문에 iterator()를 직접 호출할 수 없다.

그 대신 keySet() 이나 entrySet() 메서드를 이용해 Set 형태로 반환된 컬렉션에 iterator()를 호출하여 반복자를 만든 후, 반복자를 통해 순회할 수 있다.


Hashtable

Hashtable은 HashMap과 내부 구조가 동일하며, 사용 방법 또한 매우 유사하다.
(cf. 둘의 차이점은 스레드와 관련 있는데 아직 안배웠으니 일단 skip)

로그인 기능 예제

import java.util.*;

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

        Hashtable<String, String> map = new Hashtable<String, String>();

        map.put("Spring", "345");
        map.put("Summer", "678");
        map.put("Fall", "91011");
        map.put("Winter", "1212");

        System.out.println(map);

        Scanner scanner = new Scanner(System.in);

        while (true) {
            System.out.println("아이디와 비밀번호를 입력해 주세요");
            System.out.println("아이디");
            String id = scanner.nextLine();

            System.out.println("비밀번호");
            String password = scanner.nextLine();

            if (map.containsKey(id)) {
                if (map.get(id).equals(password)) {
                    System.out.println("로그인 되었습니다.");
                    break;
                } 
                else System.out.println("비밀번호가 일치하지 않습니다. ");
            } 
            else System.out.println("입력하신 아이디가 존재하지 않습니다.");
        }
    }
}

컬렉션 클래스 정리


오늘의 정리

열거형(Enum)

  • Enum의 기본적인 개념

    • 상수들의 집합
  • Enum이 등장하게 된 배경

    • Enum을 사용하지 않고 상수들을 선언, 사용하려면 public static final 을 통해 전역변수로 상수를 설정하고 사용해야 하는데 상수명이 중복되는 경우에서 생길 수 있는 오류를 방지하기 위해 인터페이스를 사용해야하고 또 타입 안정성을 위해 객체를 생성해 하용해야 하다보니 코드가 너무 길어지고 switch문을 사용할 수 없는 등 한계점과 가독성을 위해 등장하게 되었다.
  • Enum의 장점

    • 가독성 좋음
    • switch문 사용 가능

제네릭(Generic)

  • 제네릭의 장점

    • 단순한(간단한) 코드로 여러 타입의 데이터를 저장할 수 있는 인스턴스를 만들 수 있다.
  • 제네릭 클래스의 정의

    • 제네릭이 사용된 클래스
class Basket<T> {
    private T item;

    ...
}
  • 제네릭 메서드의 정의
    • 제네릭이 사용된 메서드
class Basket {
		...
		public <T> void add(T element) {
				...
		}
}

예외 처리(Exception Handling)

  • 프로그래밍에서 예외 처리란?

    • 에러에 인한 프로그램의 비정상적인 종료를 방지하고, 정상적인 실행 상태를 유지하는 것
  • 컴파일 에러와 런타임 에러의 차이

    • 컴파일 에러 = 컴파일 할 때 발생, 컴파일러가 감지
    • 런타임 에러 = 실행 할 때 발생, JVM이 감지
  • try-catch 문과 예외 전가

    • try-catch: 예외가 발생할 때 수행할 코드를 작성하는 것
    • 예외 전가: 예외가 발생할 때 직접 처리하지 않고 호출한 곳으로 떠넘기는 것
  • throws 키워드와 throw 키워드의 차이

    • throw : 에러를 고의로 발생시킬 때 사용
    • throws : 자신을 호출한 상위 메소드로 에러를 던질 때 사용

인스턴스 타입이 제한되는 경우 때문에 Interator을 쓰는건가?

0개의 댓글