컬렉션의 개념
배열(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개의 정수 팝
}
}
'프로그래밍 > Java 공부' 카테고리의 다른 글
연산자, 조건문(if, else if) (0) | 2021.05.20 |
---|---|
변수, 데이터 타입, 형변환 (0) | 2021.05.19 |
입출력 스트림과 파일 입출력 (0) | 2020.12.29 |
모듈과 패키지 개념, 자바 기본 패키지 (0) | 2020.12.13 |
JAVA 상속 (0) | 2020.12.12 |