프로그래밍/Java 공부

제네릭과 컬렉션

개발계발게발 2020. 12. 22. 22:57
반응형

컬렉션의 개념

배열(array)

- 고정 크기 이상의 객체를 관리할 수 없다.

- 배열의 중간에 객체가 삭제되면 응용프로그램에서 자리를 옮겨야 한다.

컬렉션(collection)

: 요소들의 리스트나 집합을 관리하는 자료 구조

- 가변 크기로서 객체의 개수(크기)를 염려할 필요 없다. = 요소의 추가, 삭제, 검색이 쉽다.

- 컬렉션 내의 한 객체가 삭제되면 컬렉션이 자동으로 자리를 옮겨준다.

ex ) Vector<E>, ArrayList<E>, HashMap<K, V>, LinkedList<E>, Stack<E>

1. 제네릭(generics) 기법

- 컬렉션 클래스의 이름에는 <E>, <K> (키), <V> (값) 등이 항상 포함

- E에 구체적인 타입을 지정할 수 있고 (ex) Vector<Integer>, 컬렉션을 일반화시키기 위해 <E>를 사용하는 것

E : 일반화시킨 타입, 제네릭 타입(generic type)

- 여러 타입의 값을 다룰 수 있도록 변신이 가능한 자료 구조이지만, 컬렉션을 사용할 때는 지정된 특정 타입의 값만 저장 가능

2. 컬렉션의 요소는 객체들만 가능

Vector<int> v = new Vector<int>(); //컴파일 오류. int는 사용 불가
Vector<Integer> v = new Vector<Integer>(); //정상 코드

기본 타입의 값이 삽입되면 자동 박싱(auto boxing)에 의해 Wrapper 클래스 타입으로 변환되어 객체로 저장됨

제네릭

제네릭 : 클래스 코드를 찍어내듯이 생산할 수 있도록 일반화(generic)시키는 도구

∴ 제네릭에 구체적인 타입을 지정하여 특정 타입으로 변신하여 사용 가능

- E : Element를 의미하며 컬렉션에서 요소임을 나타냄

- T : Type

- V : Value

- K : Key

장점 )

- 동적으로 타입이 결정되지 않고 컴파일 시에 타입이 결정되므로 보다 안전한 프로그래밍 가능

- 런타임 타입 충돌 문제 방지

- 개발 시 타입 캐스팅 절차 불필요

- ClassCastException 방지

제네릭 컬렉션 활용

 

Vector<E>

import java.util.Vector;

public class VectorEx {
	public static void main(String[] args) {
		Vector<Integer> v = new Vector<Integer>(); 
		v.add(Integer.valueOf(5));
		v.add(4);	// 4 -> new  Integer(5)로 자동 박싱
		v.add(-1);
		
		// 벡터 중간에 삽입하기
		v.add(2, 100);	// 4와 -1 사이에 정수 100 삽입
		
		System.out.println("벡터 내의 요소 객체 수 : " + v.size());	// 크기 3
		System.out.println("벡터의 현재 용량 : " + v.capacity());	// 벡터 용량 10
		
		// 모든 요소 정수 출력하기
		for(int i=0; i<v.size(); i++) {
			int n = v.get(Integer.valueOf(i));	// 벡터의 i 번째 정수
			System.out.println(n);
		}
		
		for(int i=0; i<v.size(); i++) {
			int n = v.get(i);	// 벡터의 i 번째 정수 자동 언박싱
			System.out.println(n);
		}
		
		// 벡터 속의 모든 정수 더하기
		int sum = 0;
		for(int i=0; i<v.size(); i++) {
			int n =v.get(i);
			sum += n;
		}
		System.out.println("벡터에 있는 정수의 합 : "+ sum);
	}
}

실행결과

 

 

Point의 객체만 다루는 벡터 작성

import java.util.Vector;

class Point{
	private int x, y;
	public Point(int x, int y) {
		this.x = x; this.y = y;
	}
	public String toString() {		// 현 객체에 대한 문자열 표현을 리턴
		return "(" + x +"," + y + ")";
	}
}
public class PointVectorEx {
	public static void main(String[] args) {
		Vector<Point> v = new Vector<Point>();	// Point 객체를 요소로 다루는 벡터 생성
		
		// 3 개의 Point 객체 삽입
		v.add(new Point(2,3));
		v.add(new Point(-5,20));
		v.add(new Point(30,-8));
		
		v.remove(1);	// 인덱스 1의 Point(-5, 20) 객체 삭제
		
		// 벡터에 있는 Point 객체 모두 검색하여 출력
		for(int i=0; i<v.size(); i++) {
			Point p = v.get(i);	// 벡터의 i 번째 Point 객체 얻어내기
			System.out.println(p);	// p.toString()을 이용 하여 객체 p 출력
		}
	}
}

실행결과

컬렉션을 매개변수로 받는 메소드

pulbic void printVector(Vector<Integer> v){
	for(int i=0; i<v.size(); i++){
    	int n = v.get(i);	// 벡터의 i 번째 정수
        System.out.println(n);
	}
} 

 

메소드 호출 코드

Vector<Integer> v = new Vector<Integer>();	//	Integer 벡터 생성
printVector(v);

 

자바 타입 추론 기능 진화, Java7, Java 10

 

제네릭 컬렉션 사용 객체 생성 문법

Vector<Integer> v = new Vector<Integer>();	// Java 7 이전

 

<>내(다이어몬드 연산자)에 타입 매개변수를 생략하면 컴파일러가 추론

Vector<Integer> v = new Vector<>(); // Java 7부터 

 

Java 10부터 var 키워드 도입하여 컴파일러 에게 변수 타입 추론

var v = new Vector<INteger>(); // Java 10부터

 

ArrayList<E>

 

ArrayList는 가변 크기의 배열을 구현한 컬렉션 클래스 Vector 클래스와 거의 동일

스레드 간에 동기화를 지원하지 않기 때문에,

다수의 스레드가 동시에 ArrayList에 요소를 삽입하거나 삭제할 때 데이터 훼손 우려

하지만 멀티스레드 동기화를 위한 시간 소모가 없기 때문에

ArrayList는 Vector보다 속도가 빠르다, 단일 스레드 응용에는 더 효과적

ArrayList는 벡터와 달리현재 용량을 리턴하는 메소드가 없다.capacity() 메소드 없음

 

import java.util.*;


public class ArrayListEx {
	public static void main(String[] args) {
		//	문자열만 삽입 가능 한 ArrayList 생성
		ArrayList<String> a = new ArrayList<String>();
		
		//	ArrayListr<String> a = new ArrayList<>();
		//	var a = new ArrayList<String>();
		
		// 키보드로부터 4개의 이름 입력받아 ArrayList에 삽입
		Scanner sc = new Scanner(System.in);	// Scanner 객체 생성
		for(int i=0; i<4; i++) {
			System.out.print("이름을 입력하세요>>");
			String s = sc.next();	// 이름 입력
			a.add(s);	// ArrayList 컬렉션에 삽입
		}
		
		// ArrayList에 들어 잇는 모든 이름 출력
		for(int i=0; i<a.size(); i++) {
			String name = a.get(i);	//ArrayList의 i 번째 문자열 얻어오기
			System.out.print(name + " ");
		}
		
		// 가장 긴 이름 출력
		
		int longestIndex = 0;	// 현재 가장 긴 이름이 있는 ArrayList 내의 인덱스
		for(int i=1; i<a.size(); i++) {
			if(a.get(longestIndex).length() < a.get(i).length())	// 이름 길이 비교
				longestIndex = i;
		}
		System.out.println("\n가장 긴 이름은 : "+a.get(longestIndex));
		sc.close();		
	}
}

 

컬렉션의 순차 검색을 위한 Iterator

 

-Vector, ArrayList, LinkedList, Set과 같이 요소가 순서대로 저장된 컬렉션에서

요소를 순차적으로 검색할때는 Iterator<E> 인터페이스를 사용 하면 편리

메소드 설명
boolean hasNext() 방문할 요소가 남아 있으면 true 리턴
E next() 다음 요소 리턴
void remove() 마지막으로 리턴된 요소 제거

Iterator 사용

Vector<Integer> v = new Vector<Integer>();	// 요소가 Integer 타입인 벡터

벡터 v의 iterator()를 호출하여, 벡터 v의 각 요소를 순차적으로 검색할 수 있는 Iterator 객체 - 반복자

Iterator<Integer> it = v.iterator();	// 벡터 v의 요소를 순차 검색할 Iterator 객체 리턴

 

while(it.hasNext()){	// it로 벡터의 끝까지 반복
	int n = it.next();	// it가 가리키는 요소 리턴. it의 요소 타입은 Integer이므로 정수 리턴
    ....
}

 

import java.util.*;

public class IteratorEx {

	public static void main(String[] args) {
		// 정수 값만 다루는 제네릭 벡터 생성
		Vector<Integer> v = new Vector<Integer>();
		//Vector<Integer> v = new Vector<>();
		//var v = new Vector<Integer>();
		
		v.add(Integer.valueOf(5));
		v.add(4); // 4 -> new Integer(5)로 자동 박싱
		v.add(-1);

		// 벡터 중간에 삽입하기
		v.add(2, 100); // 4와 -1 사이에 정수 100 삽입
		
		// Iterator를 이용한 모든 정수 출력하기
		Iterator<Integer> it = v.iterator();	// Iterator 객체 얻기
		while(it.hasNext()){
			int n = it.next();
			System.out.println(n);
		}
		
		// Iterator를 이용한 모든 정수 합하기
		int sum = 0;
		it = v.iterator();	// it 재설정 
		while(it.hasNext()) {
			int n= it.next();
			sum +=n;
		}
		System.out.println("벡터에 있는 정수 합 : "+sum);		
	}
}

실행결과

HashMap<K, V>

- 키(key) 와 값(value)의 쌍으로 구성되는 요소

HashMap<String, String> h = new HashMap<String, String>();	// 해시맵 생성
h.put("apple", "사과");			// "apple" 키와 "사고" 값의 쌍을 h에 삽입
String kor = h.get("apple");	  // "apple" 키로 값 검색. kor는 검색된 값, "사과"

해시맵의 장단점

1. 요소의 삽입, 삭제 시간이 매우 빠르다. ∵ 요소의 삽입 삭제 시 다른 요소들의 위치 이동이 필요 없기 때문

2. 요소 검색은 더욱 빠르다.

3. 인덱스를 이용하여 요소에 접근할 수 없고 오직 '키' 로만 검색해야한다.

∴ 빠른 삽입과 검색이 필요한 응용에 적합

∴ '키'로 '값' 읽기 !! '키'를 전달해서 '값(요소)'을 얻는다.

4. 삽입된 순서로 저장되지 않는다.

HashMap<String, String> h = new HashMap<String, String>();
	//Java 7부터 HashMap<String, String> h = new HashMap<>();
	//JAVA 10부터 var h = new HashMap<String, String>();

- 해시맵의 전체 검색

= 해시맵에 들어 있는 요소들을 모두 알아내기

1. 모든 '키'를 알아낸 후,

2. 각 '키'에 대해 하나씩 '값'을 알아내는 방식으로 작성

HashMap의 keySet( ) 메소드는 모든 '키'를 Set 컬렉션으로 만들어 리턴한다.

Set<String> keys = h.keySet(); //해시맵 h에 있는 모든 키를 Set 컬렉션으로 리턴
Iterator<String> it = keys.iterator(); //Set의 각 문자열을 순차 검색하는 Iterator(=반복자) 객체 리턴
//이 객체를 이용하여 인덱스 없이 컬렉션의 요소에 대해 순차 검색 가능
while(it.hasNext()) {
	String key = it.next(); //여기서 it으로 키를 하나씩 돌아가면서 차례대로 얻음
    String value = h.get(key); //값 얻기
    System.out.println("(" + key + "," + value + ")"); //키와 값의 쌍 출력
} //출력된 결과는 삽입된 순서와 다를 수 있음 !! = 다름 !

 

import java.util.*;

public class HashMapDicEx {
	public static void main(String[] args) {
		HashMap<String, String> dic = new HashMap<String, String>(); //해시맵 생성
			//var dic = new HashMap<String, String>();	간략히 가능
		
		// 3개의 (key, value) 쌍을 dic에 저장
		dic.put("baby", "아기");
		dic.put("love", "사랑");
		dic.put("apple", "사과");
		
		// 사용자로부터 영어 단어를 입력받고 한글 단어 검색. "exit" 입력받으면 종료
		Scanner sc = new Scanner(System.in);
		while(true) {
			System.out.print("찾고 싶은 단어는?");
			String eng = sc.next();
			if(eng.equals("exit")) {
				System.out.println("종료합니다.");
				break;
			}
			//해시맵에서 '키' eng의 '값' kor 검색
			String kor = dic.get(eng);
			if(kor == null)
				System.out.println(eng + "는 없는 단어 입니다.");
			else
				System.out.println(kor);
		}
		sc.close();		
	}
}

 

import java.util.*;

public class HashMapScoreEx {
	public static void main(String[] args) {
		// 이름과 점수를 저장할 HashMap 컬렉션 생성
		HashMap <String, Integer> scoreMap = new HashMap<String, Integer>();
			// var scoreMap = new HashMap<String, Integer>();
		
		// 5개의 이름 및 점수 저장
		scoreMap.put("김성동", 97);
		scoreMap.put("황기태", 88);
		scoreMap.put("김남윤", 98);
		scoreMap.put("이재문", 70);
		scoreMap.put("한원선", 99);
		
		System.out.println("HashMap의 요소 개수 :"+ scoreMap.size());
		
		//모든 사람의 점수 출력. scoreMap에 들어 있는 모든 (key, value) 쌍 출력
		Set<String> keys = scoreMap.keySet();	// 모든 key 를 가진 set 컬렉션 리턴
		Iterator<String> it = keys.iterator();	// Set에 있는 모든 ket를 순서대로 검색하는 Iterator 리턴
		
		while(it.hasNext()) {
			String name = it.next();		// 다음 키, 학생 이름
			int score = scoreMap.get(name);	// 점수 알아내기
			System.out.println(name + " : " + score);
		}
	}
}

실행결과

 

HashMap에 객체 저장, 학생 정보 관리

import java.util.*;

class Student{
	private int id;
	private String tel;
	public Student(int id, String tel) {
		this.id = id; this.tel = tel;
	}
	public int getIn() {return id;}
	public String getTel() {return tel;}	
}

public class HashMapStudentEx {
	public static void main(String[] args) {
		// (학생이름, Student 객체)를 저장하는 해시맵 생성
		//HashMap<String, Student> map = new HashMap<String, Student>();
		var map = new HashMap<String, Student>();
		map.put("황기태", new Student(1, "010-111-1111"));
		map.put("이재문", new Student(2, "010-222-2222"));
		map.put("김남윤", new Student(3, "010-333-3333"));
		
		Scanner sc = new Scanner(System.in);
		while(true) {
			System.out.print("검색할 이름?");
			String name = sc.nextLine();
			if(name.equals("exit"))
				break;
			Student student = map.get(name);	// 이름에 해당하는 Studnet 객체 검색
			if(student == null)
				System.out.println(name + "은 없는 사람입니다.");
			else
				System.out.println("id:" + student.getIn()+ ", 전화:"+student.getTel());
		}
		sc.close();
	}
}

 

LinkedList<E>

 

List<E> 인터페이스를 구현한 클래스

LinkedList는 요소들을 양방향으로 연결하여 관리한다는 점을 제외하고 Vector, ArrayList와 거의 같다.

 

LinkedList는 맨 앞과 맨 뒤를 가리키는 head, tail 레퍼런스를 가지고 있어, 맨 앞이나 맨 뒤, 중간에 요소 삽입 가능

인덱스를 이용하여 요소에 접근 가능

 

 

Collections 클래스 활용

 

 sort() - 컬렉션이 포함된 요소들의 정렬

 reverse() - 요소를 반대 순으로 정렬

 max(), min() - 요소들의 최댓값과 최솟값 찾아내기

 binarySearch() - 이진 검색

 

Collections 클래스의 메소드는 모두 static 타입이므로 Clooections 객체를 생성할 필요는 없다.

 

import java.util.*;

public class CollectionsEx {
	static void printList(LinkedList<String> l) {//리스트의 요소를 모두 출력하는 메소드
		
		Iterator<String> iterator = l.iterator(); // Iterator 객체 리턴
		while (iterator.hasNext()) { // Iterator 객체에 요소가 있을 때까지 반복
			String e = iterator.next(); // 다음 요소 리턴
			String separator;
			if (iterator.hasNext())
				separator = "->"; // 마지막 요소가 아니면 -> 출력
			else
				separator = "\n"; // 마지막 요소이면 줄바꿈
			System.out.print(e+separator);			
		}	
	}
		
	public static void main(String[] args) {
		LinkedList<String> myList = new LinkedList<String>(); // 빈 리스트 생성
		myList.add("트랜스포머");
		myList.add("스타워즈");
		myList.add("아바타");
		myList.add(0,"터미네이터");
		myList.add(2,"매트릭스");
		
		printList(myList);
		int index = Collections.binarySearch(myList, "스타워즈") + 1;
		System.out.println("스타워즈는 " + index + "번째 요소입니다.");
		
		Collections.sort(myList); // 요소 정렬
		
		printList(myList);
		index = Collections.binarySearch(myList, "스타워즈") + 1;
		System.out.println("스타워즈는 " + index + "번째 요소입니다.");
		
		Collections.reverse(myList); // 요소의 순서를 반대로 구성
		printList(myList);
		 
		index = Collections.binarySearch(myList, "아바타") + 1;
		System.out.println("아바타는 " + index + "번째 요소입니다.");
	}

}

 

 

 

제네릭 클래스

public class MyClass<T> { //제네릭 클래스 Myclass, 타입 매개변수 T
  T val; //변수 val의 타입은 T
  void set(T a) {
    val = a; //T 타입의 값 a를 val에 지정
  }
  T get(){
  	return val;	// T 타입의 값 val 리턴
    }
}

 

제네릭 클래스에 대한 레퍼런스 변수 선언

MyClass<String> s; // <T>를 String으로 구체화
List<Integer> li;  // <E>를 Integer로 구체화
Vector<String> vs; // <E>를 String으로 구체화

제네릭 객체 생성 - 구체화(specializaion)

제네릭 클래스에 구체적인 타입을 대입하여 구체적인 객체를 생성하는 과정을 구체화라고 부른다

MyClass<T>에서 T에 구체적인 타입을 지정하여 객체를 생성하는 예

 

MyClass<String> s = new MyClass<String>();	// 제네릭 타입 T를 String으로 구체화
s.set("hello");
System.out.println(s.get());	// "hello" 출력

MyClass<Integer> n = new MyClass<Integer>(); // 제네릭 타입 T를 Integer로 구체화
n.set(5);
System.out.println(n.ger()); // 숫자 5 출력

 

public class MyClass<String> {
  String val; //변수 val의 타입은 String
  void set(String a) {
    val = a; //String 타입의 문자열 a를 val에 지정
  }
  String get(){
  	return val;	// String 타입의 문자열 val 리턴
    }
}

 

제네릭의 구체화에는 기본 타입 사용할 수 없음

Vector<int> vi = new Vector<int>();	// 컴파일 오류. int 사용 불가
Vector<Integer> vi = new Vector<Integer>();	// 정상 코드

 

- 제네릭 타입을 가진 객체와 배열의 생성은 허용되지 않는다

public class MyVector<E> { 
  E create() { 
    E a = new E(); //컴파일 오류. 제네릭 타입의 객체 생성 불가 
    return a; 
  } 
}

1. 컴파일러가 MyVector<E> 클래스의 new E( ) 라인을 컴파일할 때, E에 대한 구체적인 타입을 알 수 없어, 호출된 생성자를 결정할 수 없고 객체 생성 시 어느 크기의 메모리를 할당해야 할 지 전혀 알 수 없기 때문

타입 매개 변수의 타입으로 강제 캐스팅하여 리턴해야 한다.

 

 

return (T)stck[tos]; //타입 매개변수 T 타입으로 캐스팅

- 제네릭 타입의 배열 선언은 허용 / 제네릭 클래스 또는 인터페이스 타입의 배열은 선언 불가

 

 

 

제네릭 메소드 작성시

class GenericMethodEx {
  static <T> void toStack(T[] a, GStack<T> gs) {
    for (int i = 0; i < a.length; i++) {
      gs.push(a[i]);
    }
  }
}
//컴파일러가 메소드의 인자를 통해 타입 유추 가능
 <T> void printArray(T[] A){
        for(int i=0;i<A.length;i++){
            System.out.println(A[i]);
        }
    }
//여기서 parameter A는 generic array임

 

class GStack<T>{	// 제네릭 스택 선언. 제네릭 타입 T
	int tos;
	Object [] stck;	// 스택에 요소를 저장할 공간 배열
	public GStack(){
		tos = 0;
		stck = new Object [10];
	}
	public void push(T item) {
		if(tos == 10)	// 스택이 꽉 차서 더 이상 요소를 삽입할 수 없음
			return;
		stck[tos] = item;
		tos++;
	}
	public T pop() {
		if(tos == 0) // 스택이 비어 있어 꺼낼 요소가 없음
			return null;
		tos--;
		return (T)stck[tos]; // 타입 매개변수 타입으로 캐스팅
	}
}
public class MyStack {
	public static void main(String[] args) {
		GStack<String> stringStack = new GStack<String>(); // String 타입의 GStack 생성
		
		stringStack.push("seoul");
		stringStack.push("busan");
		stringStack.push("LA");
		
		for(int n = 0; n<3; n++)
			System.out.println(stringStack.pop()); // stringStack 스택에 있는 3개의 문자열 팝
		
		GStack<Integer> intStack = new GStack<Integer>(); // Integer 타입의 GStack 생성
		intStack.push(1);
		intStack.push(3);
		intStack.push(5);
		
		for(int n=0; n<3; n++)
			System.out.println(intStack.pop()); // intStack 스택에 있는 3개의 정수 팝

	}

}

 

 

반응형