본문 바로가기
Study/JAVA

[JAVA] 12주차_예외 처리와 제네릭 프로그래밍 (제네릭 프로그래밍, List 컬렉션, Stack, ArrayList, LinkedList)_자바 스터디

by 8희 2022. 5. 21.

1차시 제네릭 프로그래밍

 


제네릭의 개념과 필요성

- 자바는 다양한 종류의 객체를 관리하는 컬렉션이라는 자료구조를 제공

- 초기에는 모든 객체를 담을 수 있는 Object 타입의 컬렉션 사용

- Object 타입은 실행하기 전에는 클래스에 어떤 객체를 담았는지 알 수 없다는 문제점이 나타남!

- 제네릭은 일반화라고 보면 됨

 

/* Object 타입의 컬렉션을 사용한 예시 */

//Beer 클래스
public class Beer {
}

//Boricha 클래스
public class Boricha {
}

//어떤 음료수든 담을 수 있는 Cup 클래스
public class Cup {
	private Object beverage; //모든 종류의 객체를 Cup 객체에 담을 수 있도록 Object 타입 사용

	public Object getBeverage() { //객체가 Object니까 접근자도 Object
		return beverage;
	}

	public void setBeverage(Object beverage) { //설정자의 받는 값도 Object
		this.beverage = beverage;
	}
}

 

/* 제네릭이 아닌 Cup 클래스 */

import Cup; //무엇이든 담을 수 있는 Cup 클래스 선언

public class GenericClass1Demo {
	public static void main(String[] args) {
		Cup c = new Cup();
        
        	//Cup 객체에 모든 객체를 담을 수 있으므로 Boricha 객체와 Beer 객체만 담을 수 있음
		c.setBeverage(new Boricha()); 
		
        	//Beverage 객체는 하나만 가질 수 있음
        	//따라서 Cup 객체는 Beer 객체만 남아 있음
		c.setBeverage(new Beer());
		
        	//getBeverage()는 Object 타입을 반환하므로 Beer 타입으로 변환됨
		Beer b1 = (Beer) c.getBeverage(); //다운캐스팅 발생

		//Cup에 있는 Beer 객체를 Boricha 타입으로 변환하므로 실행 오류 발생
		Boricha b2 = (Boricha) c.getBeverage(); //오류 발생 지점
	}
}

 


Generic 타입 (제네릭 타입)

- Generic 타입은 하나의 코드를 다양한 타입의 객체에 재사용하는 객체 지향 기법

- 클래스, 인터페이스, 메서드를 정의할 때 타입을 변수로 사용

- Generic 타입의 장점 두 가지

  1) 컴파일할 때 타입을 점검하기 때문에 실행 도중 발생할 오류 사전 방지

  2) 불필요한 타입 변환이 없어 프로그램 성능 향상

 

제네릭 클래스와 인터페이스의 사용

- 타입 매개변수는 객체를 생성할 때 구체적인 타입으로 대체

 

제네릭 클래스와 인터페이스의 선언 방법

 

전형적인 타입 매개변수

 

제네릭 객체 생성

- 적용할 타입은 참조 타입만 사용 가능, 기초 타입 사용 불가능!

 

/* 제네릭 Cup 클래스 */

public class Cup<T> { //타입 매개변수 명시
	private T beverage; //필드 (타입매개변수 T)

	public T getBeverage() { //메서드 (타입매개변수 T)
		return beverage;
	}

	public void setBeverage(T beverage) { //메서드 (타입매개변수 T)
		this.beverage = beverage;
	}
}

 

/* 제네릭 Cup 클래스의 테스트 */

import Cup; //제네릭 타입의 Cup 클래스 선언

public class GenericClass2Demo {
	public static void main(String[] args) {
		Cup<Boricha> c = new Cup<Boricha>(); //Boricha 타입의 Cup 객체 생성 (제네릭 객체 생성)
        	//타입매개변수가 Boricha!

		c.setBeverage(new Boricha()); 
		
        	//Boricha 타입의 Cup 객체여서 Beer 타입의 객체 담기 불가능
		//c.setBeverage(new Beer());
		
        	//Cup 객체에 있는 Boricha 객체를 Beer 타입매개변수에 대입 불가능
		//Beer b = c.getBeverage();

		Boricha b = c.getBeverage(); //Boricha 객체가 반환되므로 타입 변환 필요 없음
        	//타입이 이미 타입매개변수로 고정돼 있기 때문에 타입 변환 발생 X
	}
}

 


제네릭과 Raw 타입

- 이전 버전과 호환성을 유지하려고 Raw 타입 지원

- 제네릭 클래스를 Raw 타입으로 사용하려면 타입 매개변수를 쓰지 않기 때문에 Object 타입이 적용

 

import Cup; //제네릭 타입의 Cup 클래스 선언

public class GenericClass3Demo {
	public static void main(String[] args) {
    		//구체적인타입이 없으므로 Raw 타입의 제네릭 클래스 사용
		Cup c1 = new Cup(); //이건 Objext 타입이 적용

		//Raw 타입의 Cup 객체이므로 어떤 타입의 객체든 추가 가능
		c1.setBeverage(new Beer());

		//어떤 타입이 반환되는지 알 수 없으므로 타입변환 필요
		//Beer beer = c1.getBeverage();
		Beer beer = (Beer) c1.getBeverage(); //다운캐스팅 발생
	}
}

 


2개 이상의 타입 매개변수

- 제네릭 클래스는 타입매개변수를 2개 이상 가질 수 있다.

 

/* 다중 타입 매개변수 제네릭 클래스 */

public class Entry<K, V> {
	private K key; //변수 key의 타입은 K로 선언
	private V value; //변수 value의 타입은 V로 선언

	public Entry(K key, V value) {
		this.key = key;
		this.value = value;
	}

	public K getKey() { //변수 key를 반환하므로 반환 타입이 K
		return key;
	}

	public V getValue() { //변수 value를 반환하므로 반환 타입이 V
		return value;
	}
}

 

/* 다중 타입 매개변수 제네릭 클래스 - Entry 클래스 사용 */

public class EntryDemo {
	public static void main(String[] args) {
    		//<> 안은 왼쪽은 반드시 표기, 오른쪽은 생략 가능!
		Entry<String, Integer> e1 = new Entry<>("김선달", 20); //객체 생성
		Entry<String, String> e2 = new Entry<>("기타", "등등"); //객체 생성

		//타입 매개변수에는 기초 타입 사용 불가능! 포장 클래스로 바꾸면 사용 가능
		//Entry<int, String> e3 = new Entry<>(30, "아무개");

		System.out.println(e1.getKey() + " " + e1.getValue());
		System.out.println(e2.getKey() + " " + e2.getValue());
	}
}

 


2차시 List 컬렉션

 


컬렉션 프레임워크의 의미

- 유사한 객체의 집단을 효율적으로 관리할 수 있도록 컬렉션 프레임워크를 제공

- 컬렉션: 데이터를 한곳에 모아 편리하게 저장 및 관리하는 가변 크기의 객체 컨테이너 

            여러 데이터(객체)를 담을 수 있는 자료구조, 다수의 데이터그룹

- 프레임워크: 표준화된 체계적인 프로그래밍 방식

- 컬렉션 프레임워크: 객체를 한곳에 모아 효율적으로 관리하고 편리하게 사용할 수 있도록 제공하는 환경

                            다수의 객체를 다루기 위한 표준화된 프로그래밍 방식

 

 

- Stack, ArrayList, Deque는 순서를 정확히 따지고 Collection, LinkedList는 순서 정확히 따지기 X

- 그니까 컬렉션은 배열보다 좀 더 변동적인 느낌!

 

컬렉션 프레임워크의 필요성

- 유사한 객체를 여러 개 저장하고 조작해야 할 때가 빈번

- 고정된 크기의 배열의 불편함

 

- 연결 리스트(앞에 있는 원소의 위치를 알고 있는 자료구조) 사용 하는 경우

- 배열보다 연결 리스트가 좀 더 많은 일을 함

 

 

- 컬렉션 프레임워크는 배열의 단점을 보완하고 객체나 데이터들을 효율적으로 관리하려고 사용

- 배열은 생성할 때 크기가 정해지고 그 크기를 넘어가면 데이터 저장 불가능, 데이터가 비어있으면 메모리 낭비

- 컬렉션은 생성할 때 특정 용량을 할당할 필요 X 데이터가 추가 및 제거될 때 자동으로 크기가 맞춰진다.

 

컬렉션 프레임워크의 구조

- 컬렉션 프레임워크는 인터페이스와 클래스로 구성

- 인터페이스는 컬렉션에서 수행할 수 있는 각종 연산을 제네릭 타입으로 정의해

  유사한 클래스에 일관성 있게 접근하게 한다

- 클래스는 컬렉션 프레임워크 인터페이스를 구현한 클래스

 

컬렉션 프레임워크의 구조

 


Collection 인터페이스

- 컬렉션 프레임워크는 Collection 인터페이스와 Map 인터페이스로 나뉜다.

- Collection 인터페이스에는 List, Queue, Set이 있다.

 

Collection 인터페이스와 구현 클래스

 

Collection 인터페이스가 제공하는 주요 메서드

 

List 컬렉션 (Collection 인터페이스의 종류)

- 순서가 있는 객체를 중복 여부와 상관없이 저장하는 리스트 자료구조 지원 (중복 허용)

- 배열과 매우 유사하지만 배열과 달리 크기가 가변적

 

 

List 인터페이스가 제공하는 주요 메서드

 

배열 타입 <-> List 타입

1. 배열 타입 -> List 타입

: 왼쪽의 자료형이 List, 오른쪽의 자료형이 배열 / alist가 배열을 List로 변환

2. List 타입 -> 배열 타입

: List 타입에서 배열 타입으로 변환할 땐 size 할당 신경 쓰기!

 

/* List 인터페이스 활용 */

import java.util.Arrays; //배열 타입을 List 타입으로 변환할 때 사용하기 위해 선언
import java.util.List; //List 인터페이스 사용을 위해 선언

public class ListDemo {
	public static void main(String[] args) {
		String[] names1 = { "사슴", "호랑이", "바다표범", "곰" };
		
        	//List<포장클래스>의 형태
		List<String> list = Arrays.asList(names1); //배열을 List 타입으로 반환
        	//list는 names1이라는 배열을 list 타입으로 반환한 것을 의미!
        	//list는 객체
        	//사슴 호랑이 바다표범 곰
        
		list.set(1, "앵무새"); //list 객체의 인덱스 1번의 원소를 "앵무새"로 변경
        	//사슴 앵무새 바다표범 곰
        
		for (String s : list) //s에 list 데이터 대입
			System.out.print(s + "\t"); //s 출력
		System.out.println(); //줄바꿈 출력

		list.sort((x, y) -> x.length() - y.length()); //List 원소를 길이 순서대로 정렬
        	//곰 사슴 앵무새 바다표범
        
        	//List 타입의 객체를 배열로 변화
        	//List 객체를 배열로 변환하려면 먼저 List 객체의 크기만큼 배열 객체를 생성해야 함
		String[] names2 = list.toArray(new String[list.size()]); //사용하고 있는 size만큼 할당
        	//현재 list의 원소가 4개이므로 list.size()는 4
        
		for (int i = 0; i < names2.length; i++) //names2라는 배열의 길이(4)만큼 반복 
			System.out.print(names2[i] + "\t"); //배열 names2 출력
            		//곰 사슴 앵무새 바다표범
	}
}

 


3차시 Stack, ArrayList, LinkedList

 


 

List 컬렉션의 구현 클래스 Stack

Stack 클래스에 추가된 메서드

 

/* Stack 클래스의 활용 */

import java.util.Stack; //Stack 클래스를 사용하기 위해서 선언 필요

public class StackDemo {
	public static void main(String[] args) {
		Stack<String> s1 = new Stack<>(); //문자열 스택 생성 //s1 스택엔 문자열만 들어올 수 있음

		s1.push("사과");
		s1.push("바나나");
		s1.push("체리");

		System.out.println(s1.peek()); //스택의 최상위 원소를 제거하지는 않고 엿보기만 함 //체리

		System.out.println(s1.pop()); //스택의 최상위 원소를 하나씩 빼냄 //체리 빼냄
		System.out.println(s1.pop()); //바나나 빼냄
		System.out.println(s1.pop()); //사과 빼냄
		System.out.println();

		Stack<Integer> s2 = new Stack<>(); //정수 스택 생성 //s2 스택엔 정수만 들어올 수 있음

		s2.add(10); //스택도 벡터처럼 add() 사용 가능 //스택에 10이란 값 추가
		s2.add(20); //스택에 20이란 값 추가
		s2.add(1, 100); //스택의 [1] 인덱스에 100이란 값 추가 //앞서 추가한 20은 인덱스가 하나 뒤로 감

		for (int value : s2)
			System.out.print(value + " "); //10 100 20
		System.out.println();

		while (!s2.empty()) //빈 스택이 될 때까지 마지막 원소부터 하나씩 반환
			System.out.print(s2.pop() + " "); //20 100 10
	}
}

 

List 컬렉션의 구현 클래스 ArrayList와 LinkedList

ArrayList와 LinkedList의 비교

 

/* LinkedList와 ArrayList 클래스의 성능 비교 */

import java.util.ArrayList;
import java.util.LinkedList;

public class PerformanceDemo {
	public static void main(String[] args) {
		ArrayList<Integer> al = new ArrayList<Integer>(); //ArrayList 객체 생성
		LinkedList<Integer> ll = new LinkedList<Integer>(); //LinkedList 객체 생성

		//10만개의 원소를 ArrayList의 첫 번째 원소로 추가하는 데 소요되는 시간을 측정
		long start = System.nanoTime(); 
		for (int i = 0; i < 100000; i++)
			al.add(0, i);
		long end = System.nanoTime();
		long duration = end - start; //처리 시간 = 종료 시점 - 시작 시점
		System.out.println("ArrayList로 처리한 시간 : " + duration);

		//10만개의 원소를 LinkedList의 첫 번째 원소로 추가하는 데 소요되는 시간을 측정
		start = System.nanoTime();
		for (int i = 0; i < 100000; i++)
			ll.addFirst(i); //LinkedList가 ArrayList보다 훨씬 더 빠름
		end = System.nanoTime();
		duration = end - start;
		System.out.println("LinkedList로 처리한 시간 : " + duration);
	}
}