본문 바로가기
개발자 양성과정 필기노트/JAVA

컬렉션 프레임워크 / 제네릭

by jono 2021. 11. 19.

     I. 컬렉션 프레임워크     


- 자바에서 자료구조를 구현하여 제공하는 클래스들의 모음

 

 1. List<E> 

 1-1. 특징 

  • 배열의 형태이지만, 자동으로 저장공간이 확장된다.
  • 인덱스 O -> 저장순서가 지정
  • 데이터 중복 가능함.
  • ArrayList - 배열구조, 멀티쓰레드 X, 데이터탐색 & 순차적인 작업에 빠르다
  • Vector - 배열구조, 멀티쓰레드 O
  • LinkedList - 체인처럼 인접 참조를 링크한다. 중간 데이터의 추가/삭제는 빠르지만 순차적 작업은 느리다.

 

 1-2. 메서드 

  • add( index , object ) - 해당 인덱스에 object를 삽입함.
  • Object get(int index) - 해당 인덱스의 요소를 리턴해줌
  • Object remove(int index) - 해당 index의 데이터를 삭제하고 제거되는 요소를 리턴함
  • boolean remove(Object o) - o에 해당하는 객체를 제거함. -> 제거하고 true를 리턴함.
  • set(index, object) - 해당 인덱스의 데이터를 object데이터로 교체하고, 기존의 데이터를 리턴함.
  • List subList(from Index , to Index ) - 부분리스트 추출
  • indexOf(Object o) - 요소 o의 인덱스를 리턴함 - 앞부터 탐색
  • lastIndexOf(Object o) - 요소o의 인덱스를 끝에서부터 검사해 리턴함
  • boolean constains(Object o) - 요소 o의 존재여부 리턴

 

 1-3. 배열 전체를 List객체에 전달하기 

  • 주의) 기본데이터 타입의 배열을 전달하면 X!
  • Wrapper클래스형의 배열을 전달해야 한다.
  • Arrays.asList() 사용한다. -> 매개변수로 추가할 데이터를 전달한다.
                                    -> add() 와 달리 한번에 많은 데이터를 전달할 수 있다.
//1. List객체에 데이터를 연속적으로 전달하기
List arrayList = Arrays.asList(1,2,3,4,5);
System.out.println(arrayList);//[1, 2, 3, 4, 5, 6]

//2. 기본데이터타입 배열을 전달하면 주소값만 나온다.
int[] iArr = {1,2,3,4,5,6};
List arrList = Arrays.asList(iArr);
System.out.println(arrList); //주소값만 나오게된다.

//3. Wrapper클래스 타입의 배열을 사용해야 한다.
Integer[] intArr = {1,2,3,4,5,6};
List intArrList = Arrays.asList(intArr);
System.out.println(intArrList); //[1, 2, 3, 4, 5, 6]

 

 1-4. List 객체의 모든 요소에 접근하는 방법 

1) 향상된 for문 사용

2) 일반 for문 사용 - List객체의 get() 메서드 사용해서 출력

 

 1-5. List객체 내의 데이터를 정렬하기, 섞기 

  • 정렬 -> Collection클래스의 sort() 호출
  • 섞기 -> Collection클래스의 shuffle()
  • TreeSet 객체 -> List로 변환 & shuffle()
  • HashSet객체 -> List로 변환 & sort()

 


 2. Set<E> 

 

 2-1. 특징 

  • 인덱스 X -> 저장순서가 없이 데이터가 저장된다.
  • 데이터가 중복되지 않는다. => 중복되지 않는 난수를 생성하여 저장할 때 사용한다. (Ex. 로또번호)
  • HashSet - 정렬 X
  • TreeSet - '같은 타입'의 데이터가 저장된 Set객체의 데이터를 정렬한다.

 

 2-2. 객체생성방법 

//1. 일반적인 객체생성 방법
HashSet hs = new HashSet();

//2. 업캐스팅을 사용한 객체생성 방법 - 부모인터페이스가 대부분의 메서드를 보유하고 있기 때문.
Set set = new HashSet();

//3. Arrays.asList() 사용하여 한번에 여러 데이터 전달하기
Set setAsList = new HashSet(Arrays.asList(1,3,450,20,6));
System.out.println(setAsList);//[1, 450, 3, 20, 6]

 

 2-3. 메서드 

  • boolean isEmpty() - 컬렉션 객체가 비어있는지 판별함
  • int size() - 컬렉션 객체 내의 데이터 갯수를 리턴함
  • String toString() - 컬렉션 객체 내의 모든 요소를 문자열로 리턴함 (오버라이딩)
  • boolean add(Object o) - 요소(o) 1개를 컬렉션에 추가한 후 성공여부를 리턴함.
  • boolean contains(Object o) - 매개변수가 컬렉션에 있는지 검사함
  • boolean remove(Object o) - 매개변수를 컬렉션에서 제거함
  • Object[] toArray() - 컬렉션의 모든 요소를 담은 배열을 생성하여 리턴함.
  • boolean addAll(Collection c) - 다른 컬렉션에 매개변수로 전달한 컬렉션의 모든 요소를 추가시킴
  • void clear() - 컬렉션의 모든 요소를 초기화함

 

 2-4. Set객체 내의 데이터에 접근하는 방법 

- 인덱스 사용이 불가능하므로 일반 for문으로는 접근이 불가능하다.

=> 향상된 for문을 사용한다.

for(요소를 꺼내 저장할 변수: 컬렉션 객체){}//-> 일단 Object형으로 저장한 후 나중에 다운캐스팅함.

 


==> List , Set 객체에 접근시 Iterator 객체를 사용한다.

 

iterator() - Iterator타입 객체 리턴

hasNext() - 다음 요소 존재여부 판별

next() - 다음 요소에 접근

 

-> List, Set 둘 다 Collection 인터페이스를 구현했고, 객체의 데이터에 접근하는 방법이 완전히 똑같으므로
    별도의 메서드를 정의하여 같은 메서드로 접근할 수 있다.

 

public class Ex01 {
	public static void main(String[] args) {
	
	// 통일된 접근을 가능하게하는 static 메서드 printElements(Collection) 정의해서 불러옴
	printElements(set);
	printElements(list);
	
	}//<<<<<<<<<<<<<<<<<<<<<<main() 메서드 끝>>>>>>>>>>>>>>>>>>>>>>

	// Set, List 객체를 전달받아 내부의 모든 요소를 출력하는 메서드를 정의	
	public static void printElements(Collection c) {//부모인터페이스로 업캐스팅하여 전달받음
		Iterator iter = c.iterator();
		while(iter.hasNext()) {
			Object o = iter.next();
			System.out.println("printElements결과: "+o);
		}		
	}
    
}

 


 3. Map<K , V> 

 3-1. 특징 

  • Key & Value 한쌍의 형태를 갖는다.
  • Key - 중복 불가능 == Set 객체 형태로 관리하면 편리하다.
  • Value - 중복 가능
  • HashMap
  • Properties

 

 3-2. 메서드 

  • put(Object key, object value) - 데이터 추가. 두개 다 전달해야함.
  • get(Object key) -> 해당 key의 value를 리턴함 / 존재하지 않는 key를 지정하면 null리턴
  • Set keySet() - Map 객체 내의 모든 키를 리턴함.
  • Collection values() - Map 객체 내의 모든 값을 리턴
    - Set, List 타입의 변수에 할당이 불가능하다.
    - 다운캐스팅도 불가능
    -? 그럼 이건 왜 되는지?
    // Set객체로 관리하면 중복을 제거한 값을 출력할 수 있다.
    Set values = new HashSet(map.values()); 
    System.out.println("Set객체로 map.values()"+values); //[haha, Hello, World]
    
    // List객체로 관리하면 중복값 그대로 출력한다.
    List vals = new ArrayList(map.values()); 
    System.out.println("List객체로 map.values()"+vals); //[World, Hello, haha, haha]
  •  Set entrySet() - 키와 값을 한 쌍으로 갖는 Map.Entry객체의 모임
    - 키&값 한 쌍이 다른 키&값 한쌍과 중복될 수 있으므로 Set타입으로 관리한다.
    Set entrySet = map.entrySet();​
  • isEmpty()
  • size()
  • Object remove(Object key) - 해당 key&value 한쌍이 제거되고 해당 key의 value가 리턴된다.
  • boolean remove(Object o, Object value) - 전달한 key와 value 모두 일치하는 경우에 제거된다. 
                                                         - 둘 중 하나라도 일치하지 않으면 false리턴됨.
  • boolean containsKey(Object key) - 해당 key의 존재 여부 리턴
  • boolean containsValue(Object o) - 해당 value의 존재 여부 리턴
  • void clear() - 모든 키와 값을 초기화함

 

 3-3. 반복문을 통해 Map객체의 데이터에 접근하는 방법 

 

1) 키를 통해( keySet() ) Iterator 반복자로 접근하기

Map객체의 keySet() 호출하여 모든 키를 꺼내 Set객체에 저장한 후

Set객체에 Iterator로 접근한다.

Set keySet2 = map1.keySet();
Iterator iter = keySet2.iterator();
while(iter.hasNext()) {
	String phone = iter.next().toString();
	String name = map1.get(phone).toString();
	System.out.println("이름: "+name+", 전화번호: "+phone);
}

 

2) 키&값 한 쌍을 통해( entrySet() ) Iterator 반복자로 접근하기

Set entrySet2 = map1.entrySet();
	Iterator entryIterator = entrySet.iterator();
	while(entryIterator.hasNext()) {
		Map.Entry entry = (Map.Entry)entryIterator.next();
		//1개의 엔트리에서 getXXX() 메서드를 호출하여 각각의 키,값 가져옴
		System.out.println("이름: "+entry.getValue()+", 전화번호: "+entry.getKey());
	}

 

 

3) 향상된 for문 사용하기

Set keySet3 = map1.keySet();
	for (Object phone : keySet3) {
	System.out.println("이름: "+map1.get(phone)+", 전화번호: "+ phone);
	}

 


     II. Stack & Queue     


1. Stack

 # 특징 

- 데이터를 아래부터 위쪽으로 쌓는 구조

- 데이터 삽입/삭제가 한쪽방향으로만 일어난다. ==  First In Last Out,  Last In First Out

=> 주로 웹브라우저 뒤로, 앞으로 기능 구현시에 사용한다.

 

 # 메서드 

  • Object push(Object item) -  데이터 item을 추가하고, 해당 데이터를 리턴한다.
  • Object peek() - 스택의 맨 위, 즉 가장 마지막에 추가된 요소를 리턴함.
  • Object pop() - 스택의 맨 위, 즉 가장 마지막에 추가된 요소를 리턴하고 제거함

2. Queue

 # 특징 

- 양방향 모두에서 데이터의 이동이 발생하며, 한쪽에서 데이터 삽입, 다른 한쪽에서 데이터 삭제 발생

== First In First Out, Last In Last Out

- Queue는 인터페이스이다. 구현클래스== LinkedList

=> 주로 최근문서항목(?) 이나 선착순 대기열 등 구현시에 사용한다.

 

 # 메서드 

  • boolean offer(Object o) - 요소 o를 추가 (추가 결과를 boolean타입으로 리턴함)
  • Object peek() - 큐의 맨 끝 요소, 즉 가장 먼저 추가된 요소를 리턴한다.
  • Object poll() - 큐의 맨 끝 요소, 즉 가장 먼저 추가된 요소를 리턴하고 제거한다.

 


     III. 제네릭     


 1. 특징 

- 클래스 정의 시 데이터타입을 미리 지정하지 않고 임의의 데이터타입으로 선언한 후,

  객체 생성 시점에서 특정 데이터 타입을 지정하여 해당 데이터 타입으로 변환하는 방법이다.

ArrayList <Integer> list = new ArrayList<Integer>();

- 데이터 타입은 반드시 Wrapper클래스만 지정할 수 있다!

- Map 객체 생성시 제네릭 타입은 <Key, Value>형식으로 지정한다.

 

 2. 클래스정의시 제네릭타입 지정하기 

- 클래스 정의 시점에서 클래스명 뒤에 <가상의 데이터타입> 명시함

- 주로 <E> 또는 <T>

- 가상의 데이터타입이므로 실제 데이터타입으로 사용할 수는 없다.

- 지정된 가상의 자료형은 클래스 내부에서 실제 데이터타입으로 대체된다.

public class Ex02 {
  public static void main(String[] args) {
  	// Integer 타입으로 지정함
	GenericClass<Integer> gc = new GenericClass<Integer>();
	gc.setData(100);
    
 	 // String 타입으로 지정함
  	GenericClass<String> gc3 = new GenericClass<String>();
  	gc3.setData("Hello world");
    
  	// Person타입으로 지정함
  	GenericClass<Person> gc4 = new GenericClass<Person>();
  	gc4.setData(new Person("홍길동", 20));
  	Person p = gc4.getData();
  	System.out.println("이름: "+p.getName()+", 나이: "+p.getAge());
  }
  
  class GenericClass<T>{
      T data;

      public T getData() { return data; }

      public void setData(T data) { this.data = data; }
  }
}

 

 3. 제네릭타입 사용시 주의사항 

- static 멤버 내에서는 제네릭타입 사용이 불가하다.

  -> 제네릭 타입은 인스턴스 생성시 실제 데이터 타입으로 변환되는 반면,

      static 멤버는 인스턴스 생성 이전에 로딩되기 때문이다. 

- new 연산자 사용시 제네릭 타입 사용 불가능

T tInstance = new T(); //-> 오류난다

- instanceof 연산자에 제네릭타입 사용 불가능

  : 컴파일 시점에서 제네릭 타입의 데이터타입 확인이 불가능하기 때문이다.

Object o = new Object();
if(o instanceof T) {...} //-> instancof에 제네릭타입 데이터타입 사용은 불가능하다.

 

 4. 제네릭타입에 지정될 데이터의 종류를 제한할 수도 있다. 

class 클래스명<타입파라미터 extends 클래스타입>{...}
//-> 데이터타입은 Number클래스이거나 Number의 하위클래스만 지정이 가능하게 된다.

 

 5. 상속과 인터페이스 구현시에 제네릭타입 지정도 가능하다. 

- 동시에 서브클래스 자신만의 제네릭타입을 지정하는 것도 가능하다.

class Class1<P>{}
interface Interface1<Q>{}

// 부모클래스에 제네릭타입이 지정되어있을 경우, 서브클래스에서 상속받을 때 부모의 타입파라미터를 명시해야함.
class SubClass<P,Q,R> extends Class1<P> implements Interface1<Q>{
	P var1; //슈퍼클래스 Class1의 타입 P
	Q var2; //슈퍼인터페이스 Interface1의 타입 Q
	R var3; //서브클래스 자신의 타입 R
}

댓글