벡터(Vector)

벡터는 ArrayList와 같이 선형적으로 자료를 담을 수 있는 List 컬렉션입니다. ArrayList와는 다르게 동기화 처리를 하기 때문에 ArrayList보다는 속도가 느리다는 단점이 있습니다. 하지만 여러 쓰레드에서 같은 List를 써야할 필요가 있다면 Vector를 사용하시는 것이 동기화 오류를 만들지 않는 방법이겠죠. 

동기화에 대한 예는 맨 아래에 설명하도록 하겠습니다. 지금부터 사용법에 대해서 알아보도록 합시다.

사용법

벡터는 Generic을 사용합니다. 꺽쇠 '<', '>' 에 자료 타입을 지정해주고 수행합니다. 꺽쇠 안에는 원소로 사용할 객체의 클래스를 명시해주면 됩니다. 아래는 Integer 형의 자료를 Vector에서 다룬다는 선언을 보여줍니다.

 

 

Vector<Integer> v=new Vector();

 

1 - 원소 추가와 삭제, 읽기(add, remove, get)

선형으로 자료를 담고 있습니다. 그래서 데이터를 넣으면 차례차례 뒤에 데이터들이 붙어서 쌓이게 됩니다. 특정 위치의 데이터도 삭제할 수 있습니다. 그리고 정수형 index로 원소를 가져올 수 있습니다. 아래가 그 예제를 보여줍니다. 

import java.util.Vector;	//클래스 import
public class Main {
	
	public static void main(String[] ar){
		Vector<Integer> v=new Vector();
		v.add(20);	//0
		v.add(50);	//1
		v.add(70);	//2
		v.add(100);	//3
		
		for(int i=0;i<v.size();i++) {
			System.out.println(i+"번째 원소:"+v.get(i));
		}
		
		System.out.println("\n index 1 원소 삭제");
		v.remove(1);
		
		for(int i=0;i<v.size();i++) {
			System.out.println(i+"번째 원소:"+v.get(i));
		}
	}
}

 

결과

0번째 원소:20
1번째 원소:50
2번째 원소:70
3번째 원소:100

 index 1 원소 삭제
0번째 원소:20
1번째 원소:70
2번째 원소:100

 

예제를 보면 차례차례 20, 50, 70, 100의 데이터를 넣어주고 있습니다. 그리고 for문을 돌면서 출력해주고 있습니다. 차례대로 출력이 되는 것을 확인할 수 있죠? 그런데 index가 1인 50을 삭제하게 되면 삭제된 뒤의 원소들이 전부 앞으로 당겨지게 됩니다.

 

 

remove

만약 size()보다 큰 index를 갖는 원소를 삭제하거나 get()으로 읽어온다면 ArrayIndexOutOfBoundsException이 발생하게 되므로 size() 체크를 항상 해주셔야합니다. 맨 아래 라인에 v.remove(10); 코드를 추가해서 확인해보도록 하세요.

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10
	at java.base/java.util.Vector.remove(Vector.java:844)
	at aa.Main.main(Main.java:23)

 

원소를 모두 삭제하고 싶다면 clear(), removeAllElements()를 사용하면 모두 지워집니다.

 

2. Vector를 반복할 수 있는 여러 방법

위의 예제는 단순 for문으로 i를 인덱스 삼아서 get()으로 원소를 읽어왔습니다. 이밖에도 여러 방법이 있는데, 그 방법을 소개합니다. 

2-1 forEach 메소드 사용

	Vector<String> v=new Vector();
	v.add("korea");
	v.add("england");
	v.add("rusia");
		
	v.forEach((item)->{
		System.out.println(item);
	});

람다식을 이용해서 순회할 수 있습니다. 함수 안 괄호에는 원소가 들어갑니다. 그 변수명이 item이지요. 그리고 중괄호('{}')에는 동작부를 구현하면 됩니다. 람다식을 몰라도 사용하는 데에는 문제가 없습니다.

2-2 Iterator객체로 순회

	Iterator<String> it=v.iterator();
	while(it.hasNext()) {
		String item=it.next();
		System.out.println(item);
	}

 

사이즈를 체크하지 않으면서 순회하고 싶다면 Iterator클래스로 객체를 만들어 사용하는 방법이 있습니다. Iterator클래스는 Java.util 패키지에 존재하니 import하여 사용해보세요. 순회에 필요한 메소드는 hasNext()와 원소를 가져오는 next()메소드만 알고 있으면 됩니다.

메소드 설명
hasNext() 이 다음에 원소가 있는지 확인합니다. 있으면 true, 없으면 false를 반환하지요. 대체로 while안의 조건문에 사용합니다.
next() 다음 원소를 가져옵니다. 반환되는 객체는 Generic으로 넘겨준 원소의 자료형입니다.

위 코드의 결과는 그 이전의 결과와 같습니다.

 

3. Vector와 Vector를 합치기

 

 

public static void main(String[] ar){
		Vector<String> v1=new Vector();
		v1.add("r");
		v1.add("e");
		v1.add("a");
		v1.add("k");
		
		Vector<String> v2=new Vector();
		v2.add("w");
		v2.add("o");
		v2.add("n");
		
		v1.addAll(v2);
		v1.forEach((item)->{
			System.out.print(item);
		});
		System.out.println();
	}

 

결과

reakwon

 

addAll() 메소드로 벡터와 벡터를 합칠 수 있습니다. 단, 두 벡터는 같은 Generic 형식을 사용해야하고 합칠 벡터 뒤에 그 벡터가 붙습니다. 또는 생성자를 이용해서 객체 생성시에 벡터를 합칠 수 있습니다. 아래와 같은 형식으로 사용할 수 있습니다. 아래 예는 v2에 v1을 객체 생성시에 합칩니다.

Vector<String> v2=new Vector(v1);

 

4. 원소가 존재하는 지 확인

	public static void main(String[] ar){
		Vector<String> v=new Vector();
		v.add("r");	v.add("e");
		v.add("a"); v.add("kwon");
		
		System.out.println(v.contains("r"));
		System.out.println(v.contains("z"));
	}

 

결과

true
false

 

contains() 메소드로 Vector에 원소가 있는지 확인할 수 있습니다. 있으면 true, 없으면 false를 반환합니다.

 

5. Vector 정렬

public static void main(String[] ar){
	Vector<String> strV=new Vector();
	strV.add("reakwon");
	strV.add("hello");
	strV.add("world");
		
	//알파벳 순으로 정렬
	Collections.sort(strV);
	strV.forEach((item)->{
		System.out.println(item);
	});
		
	System.out.println();
		
	Vector<Integer> intV=new Vector();
	intV.add(5);
	intV.add(1);
	intV.add(3);
		
	//오름차순 정렬
	Collections.sort(intV);
	intV.forEach((item)->{
		System.out.println(item);
	});
}

Collections.sort() 메소드를 이용해서 Vector의 데이터를 정렬할 수 있습니다. 기본적으로 숫자는 오름차순, 문자열은 사전순으로 정렬됩니다.

결과

hello
reakwon
world

1
3
5

혹은 내가 만든 객체를 정렬하려면 어떻게할까요? 사람 객체를 키순으로 정렬하고 싶다면, 또는 이름 순으로 정렬, 나이순으로 정렬하고 싶다면 어떻게할까요? 우리가 직접 비교해서 Collections에게 알려줘야합니다. 그것에 대한 설명은 아래의 링크를 참고해주세요. 사용법은 동일합니다.

reakwon.tistory.com/91

 

[자바/JAVA] Collections와 ArrayList를 이용한 객체 정렬

정렬 우리는 정렬에 관해서 배우기도 하였고 구현도 해보았습니다. 그래서 어떻게 정렬이 되는지 알고 있죠. 하지만 실제 프로그래밍하는 상황에서 이 정렬을 직접구현해서 프로그램을 만들지

reakwon.tistory.com

6. Thread-Safe 예

아까 이야기했던 동기화에 대한 부분을 확인해보도록 합시다. 우선 ArrayList로 아래의 코드를 짜면서 관찰해보죠. 

 

 

 

public class Main {
	
	static ArrayList<String> list=new ArrayList();
	public static void main(String[] ar){
		list.add("reakwon");
		list.add("hello");
		list.add("world");
		
		Thread thread=new Thread(new Runnable() {
			@Override
			public void run() {
				list.forEach((item)->{
					//1초마다 원소를 출력
					try {
					Thread.sleep(1000);
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(item);
				});
			}
		});
		
		thread.start();	//thread 시작
		
		//thread가 forEach문을 먼저 수행할 여유를 주기 위해 1초 기다림
		try {
			Thread.sleep(1000);
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
		//thread가 forEach() 하는 중에 원소추가
		list.add("thread-unsafe");
	}
}

 

결과

reakwon
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1513)
	at aa.Main$1.run(Main.java:19)
	at java.base/java.lang.Thread.run(Thread.java:831)

 

위의 코드는 아주 간단한 코드입니다. thread라는 쓰레드와 메인 스레드는 list라는 ArrayList객체를 공유합니다. thread는 그 list를 forEach()로 1초마다 그 원소를 출력하는 역할을 하고, 메인 스레드는 스레드 생성하고 1초가 지난 다음 list에 원소를 추가하는 상황입니다. 이때 메인스레드가 thread가 forEach()를 수행하는 중에 list에 원소를 집어넣는 접근을 하게 되는데, 이때 두 스레드 동시에 list에 접근하게 됩니다. 그렇게 되면 위처럼 ConcurrentModificationException이 발생하게 되는 상황이 되죠. 어떻게 고칠까요?

ArrayList를 Vector로만 바꿔주면 이 문제는 해결됩니다.

static Vector<String> list=new Vector();

MainThread는 thread가 forEach()를 종료할때까지 Vector객체에 접근할 수 없고, 반대로 메인스레드의 add()가 끝날때까지 thread는 Vector객체에 접근할 수 없습니다.

Vector는 동작마다 동기화를 걸어줍니다. 이런 일은 속도롤 떨어지게 만드는 작업이지만 멀티 스레드 환경에서는 안전한 작업이죠. 그래서 여러 스레드가 있는 환경에서 개발한다면 Vector를 사용하고 단일 스레드 환경에서는 ArrayList를 활용하시면 되겠습니다.

이상으로 Vector에 대한 포스팅을 마칩니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

HashSet

자바 Collection 중 Set의 대표적인 HashSet 클래스를 다루어 보도록 하겠습니다. HashSet은 Set의 파생클래스로 Set은 기본적으로 집합으로 중복된 원소를 허용하지 않습니다. HashSet은 순서 역시 고려가 되지 않습니다. 그렇다면 다음의 예제로 HashSet의 기본적인 동작을 살펴보도록 하겠습니다.

 

public static void main(String[] args){
	Set hashSet=new HashSet();
	hashSet.add("F");
	hashSet.add("B");
	hashSet.add("D");
	hashSet.add("A");
	hashSet.add("C");
	
	/* 위와 같은 데이터들을 다시 add */
	hashSet.add("F");
	hashSet.add("B");
	hashSet.add("D");
	hashSet.add("A");
	hashSet.add("C");
	
	/* HasSet의 "C"라는 원소 삭제 */
	hashSet.remove("C");
	
	/* HashSet 모든 원소 출력 */
	System.out.println("HashSet 원소 출력");
	Iterator it=hashSet.iterator();
	while(it.hasNext()){
		System.out.print(it.next()+" ");
	}
	
	/* HashSet의 모든 원소를 ArrayList로 전달 */
	List arrayList=new ArrayList();
	arrayList.addAll(hashSet);
	
	/* ArrayList의 모든 원소 출력 */
	System.out.println();
	System.out.println("ArrayList 원소 출력");
	for(int i=0;i<arrayList.size();i++){
		System.out.print(arrayList.get(i)+" ");
	}
}
 

 

우선 HashSet에 "F", "B", "D", "A", "C"라는 문자열을 차례대로 넣었습니다. 그리고 나서 다시 같은 데이터들을 넣게 되죠.  Set에는 중복을 허용하지 않는다고 하였으니 Set에는 현재 { "F", "B", "D", "A", "C" }가 존재합니다.

 

그 후에 "C"라는 문자열을 지우는 군요. 뭐 쉽습니다. "C"라는 원소를 삭제했으니 { "F", "B", "D", "A"}만이 남는것을 알 수 있겠군요. HashSet을 출력하는 부분을 보고 확인해보세요.

아, HashSet에는 순서가 없으므로 Iterator를 사용해서 집합안의 원소를 출력하고 있습니다. Iterator의 메소드는 3개가 다인데요.

hasNext() : 다음 원소가 남아있는지 여부를 알아냅니다. 다음 원소가 남았다면 true를 반환합니다.

next() : 다음 원소를 가져옵니다. 

remove() : 현재 반복자가 가리키고 있는 원소를 삭제합니다.

 

장난삼아서 ArrayList에 HashSet의 원소를 모두 전달해서 확인해봤습니다. 아래는 그 결과입니다.

HashSet 원소 출력
A B D F 
ArrayList 원소 출력
A B D F 

 

이제 우리는 단순히 String 타입의 자료형이 아닌 우리가 정의한 클래스의 객체를 HashSet의 원소에 넣고 싶습니다. 그렇다면 어떻게 중복된 원소인지 아닌지를 확인할 수 있을까요?

위에서 String 객체를 HashSet은 어떻게 중복된 원소라고 감지했을까요? HashSet이 알아서 판단해 줄까요?

 

우리가 다음과 같은 Person 클래스가 있다고 합시다.

 

class Person{
	String name; //이름
	int residentNumber; //주민번호
}

 

사람이 같은지 여부를 판별하는 것은 주민등록번호를 보고 알 수 있지요? 허나 Set에 단순히 이 Person 클래스를 넘겨준다면 중복 여부를 판별하지 못할 겁니다. Set이 Person 객체의 어떤 메소드를 호출해서 같은지 말지를 판변할 수 있으니까요. 그 메소드가 바로 equals와 hashCode입니다.

 

1. equals(Object o)

만약 두 객체(현재 이 객체와 인자로 넘어온 객체 o)가 같다는 것을 알려주려면 equals 메소드에서 true를 반환해주어야합니다.

 

2. hashCode()

만약 equals에서 두 객체가 같다라고 true를 반환했다면 hashCode는 두 객체에서 항상 같은 값을 반환해야합니다. 만일 equals에서 false를 반환하여 같지 않다고 반환했다면 hashCode 역시 다르게 반환하는 것이 좋습니다.

 

위의 조건을 고려해서 구현한 Person 클래스를 다시 봅시다.

class Person{
	String name; //이름
	int residentNumber; //주민번호
	
	public Person(String name,int residentNumber){
		this.name=name;
		this.residentNumber=residentNumber;
	}
	@Override
	public int hashCode(){
		/* Objects.hash 메소드로 residentNumber의 해쉬값 반환 */
		return Objects.hash(residentNumber);
	}
	
	@Override
	public boolean equals(Object o){
		/* 주민 번호가 같은 Person은 true 반환 */
		Person p=(Person)o;
		return p.residentNumber==this.residentNumber;
	}
}

 

equals와 hashCode를 Override한 코드를 확인해봅시다.

equals에서는 단순히 residentNumber만 비교해서 같다면 true, 다르다면 false입니다.

hashCode에서는 Objects.hash 메소드로 residentNumber의 해쉬값을 반환합니다. 이 hash는 residentNumber가 다르다면 항상 다른 값이 반환됩니다.

 

 

이제 Set에 원소를 넣고 확인해보도록 하지요.

 

public static void main(String[] args){
	
	Set hashSet=new HashSet();
	hashSet.add(new Person("reakwon",111111));
	hashSet.add(new Person("KDC",222222));
	hashSet.add(new Person("KSG",333333));
	hashSet.add(new Person("reakwon",111112));
	hashSet.add(new Person("MJW",111111));
	
	Iterator it=hashSet.iterator();
	while(it.hasNext()){
		Person p=it.next();
		System.out.println(p.name+"/"+p.residentNumber);
	}
}

 

동명이인이 있군요. 주민 번호 111111인 reakwon과 주민 번호 111112인 reakwon인 객체들이네요. 우리는 이름이 같은것은 같은 객체로 보지 않습니다. 주민 번호가 같은 객체만 중복된 원소로 보는 것이죠.

그래서 이 두 객체는 집합에 추가가 됩니다.

 

하지만 이름이 주민번호가 111111의 "MJW"라는 객체와 주민번호 111111의 "reakwon"라는 객체는 같습니다. 주민번호가 같기때문입니다. 그래서 "MJW"라는 Person객체는 집합이 추가하지 않습니다.

 

그 집합의 모든 원소를 출력한 결과는 아래와 같습니다.

KSG/333333 
reakwon/111112 
reakwon/111111 
KDC/222222

 

이제 우리가 만든 객체들도 HashSet에 추가할 수 있습니다.

 

HashSet을 알아보았으니 다음은 HashMap에 대해서 알아볼 차례겠군요. 아래의 링크로 들어가서 개념과 사용법을 익혀보도록 합시다.

reakwon.tistory.com/151

 

[자바] 해시맵(HashMap)의 개념과 사용 예, 기본 메소드와 같은 키인지 판별하는 방법

해시맵(HashMap) 해시맵은 이름 그대로 해싱(Hashing)된 맵(Map)입니다. 여기서 맵(Map)부터 짚고 넘어가야겠죠? 맵이라는 것은 키(Key)와 값(Value) 두 쌍으로 데이터를 보관하는 자료구조입니다. 여기서

reakwon.tistory.com

반응형
블로그 이미지

REAKWON

와나진짜

,

컬렉션(Collection)

이름에서도 알 수 있듯이 자료들을 효율적으로 모을 수 있는 자료구조를 의미합니다. Collection의 상속구조는 아래의 그림과 같습니다.

 

주황색 상자는 인터페이스, 파란색 상자는 클래스를 의미하며 파란색 화살표는 extends, 녹색 화살표는 implements의 관계를 나타냅니다.

 

Collection이라는 인터페이스는 Collection 계층의 최상위 인터페이스입니다. Iterable이라는 인터페이스를 상속했다는 것을 알 수 있고, Map은 Collection 인터페이스를 상속하지는 않지만 자바의 JCF(Java Collections Framework)에 포함됩니다. 

 

 

Collection 인터페이스의 메소드들을 간단히 살펴보도록 하겠습니다. 별로 어려운 메소드는 없을 것 같습니다.

boolean add(E e)

요소를 추가합니다.

boolean addAll(Collection<? extends E> c)

Collection 타입의 매개변수에 있는 모든 요소를 추가합니다.

void clear()

Collection의 모든 요소를 지웁니다.

boolean contains(Object o)

인자 o가 이 Collection에 속한다면 true를 반환하고 없다면 false를 반환합니다.

boolean containsAll(Collection<?> c)

Collection c의 원소들이 이 Collection에 모두 존재한다면 true, 그렇지 않으면 false를 반환합니다.

boolean equals(Object o)

o와 같은 객체인지 아닌지 확인합니다.

int hashCode()

이 Collection의 해쉬코드를 반환합니다.

boolean isEmpty()

이 Collection이 비어있는지 확인합니다.

Iterator<E> iterator()

이 Collection의 반복자(Iterator)를 반환합니다. Set같은 Collection은 순서를 고려하지 않기때문에 iterator로 원소를 순회합니다. Iterator의 대표적인 메소드는 hasNext와 next가 있습니다.

boolean remove(Object o)

o와 같은 원소가 이 Collection에 존재한다면 삭제합니다.

boolean removeAll(Collection<?> c)

이 Collection에 Collection c를 모두 제거합니다.

boolean retainAll(Collection<?> c)

매개변수 c의 요소들만을 남겨둡니다.

int size()

원소의 사이즈를 반환합니다.

Object[] toArray()

이 Collection의 모든 요소를 포함하는 배열로 반환합니다.

<T> T[] toArray(T[] a)

지정한 타입의 배열로 변환합니다.

 

 

이제 Collection을 상속받는 List, Set, Queue와 Map에 관한 간단한 설명을 시작하도록 하겠습니다. 한 번더 이야기하자면 아래의 Collection은 전부 인터페이스로 스스로 객체생성이 불가능하다는 것을 알아두세요.

1. List

순서가 중요한 Collection입니다. 순서가 있는 데이터의 집합으로 데이터의 중복을 허용합니다. 기존의 배열과는 다르게 크기가 동적으로 변합니다.

 

파생클래스

Vector, ArrayList : 두 클래스는 같이 설명하겠습니다. 우선 기본적인 동작은 원소를 추가, 삭제하는 것은 둘 다 비슷합니다. 하지만 Vector는 동기화 처리가되어있습니다. 즉, 하나의 스레드만이 Vector에 요소를 추가하거나 삭제가 가능하지요. 스레드에 대해서 안전하지만 느리다는 단점이 있습니다. 하지만 ArrayList는 동기화가 되어있지 않습니다. 그렇기 스레드에 대해 안전하지 않습니다. 하지만 추가, 삭제가 빠르다는 장점이 있습니다.

단일쓰레드 환경에서 개발할때에는 ArrayList를 쓰는것이 바람직하다고 할 수 있습니다.

LinkedList : 자료구조를 배웠다면 이해가 수월할 것인데, 간단히 말하면 각각의 요소는 다음 요소를 가리키고 있어 추가, 삭제 연산이 빠릅니다.

 

 

2. Set

순서가 없는 데이터의 집합으로 데이터의 중복을 허용하지 않습니다. 우리가 중딩시절에 배운 집합을 떠올리면 될텐데요. 참고로 저는 집합이 나오고 수학을 포기했습니다.

 

파생클래스

HashSet : 내부적으로 해싱을 이용해서 구현된 클래스입니다. Set 파생클래스에서 가장 성능이 우수합니다.

TreeSet : 내부적으로 레드-블랙 트리 방식으로 구현된 클래스입니다. 레드- 블랙 트리는 이진 탐색 트리의 일종으로 log(n)의 속도로 삽입, 삭제, 검색이 가능합니다. HashSet보다는 성능이 느립니다.

 

3. Queue

아마 너무나도 잘 알고 있을 Queue는 기본적으로 선입 선출(First-In First-Out) 형식의 자료구조입니다. 먼저 들어온 원소가 먼저 나간다는 Collection입니다.

 

파생클래스

PriorityQueue : 들어온 순서가 아니라 우선순위 별로 Queue에서 원소를 꺼내옵니다. 우선순위는 Comparable 인터페이스로 정할 수 있습니다.

ArrayDeque : 보통의 큐와는 다르게 큐의 양쪽에서 원소를 꺼내올 수 있는 Collection입니다.

 

4. Map

Map은 키-데이터(Key-Value) 쌍으로 자료를 보관하고 있습니다. Map에서 순서는 고려되지 않는 편이며 키는 중복을 허용할 수 없습니다.

 

파생클래스

HashMap : 키-값의 쌍으로 값을 가져올 수 있는 대표적인 Map의 Collection입니다. 동기화를 보장하지 않습니다. 키-값으로 쉽게 데이터를 검색할 수 있습니다. 단일 스레드에서 개발한다면 HashMap을 사용합시다.

또한 HashMap은 특이하게도 키 또는 값에 null을 저장할 수 있다는 점입니다.

Hashtable : HashMap과 사용법이 거의 동일한데, 다른점은 동기화를 보장한다는 것입니다. 그렇기 때문에 HashMap보다는 무겁겠죠?

또 HashMap과는 다르게 키 또는 값에 null을 사용할 수 없습니다.

SortedMap : 이름에서도 알 수 있듯이 정렬이 된 Map구조입니다. 

 

다음 포스팅에는 파생클래스를 사용하는 방법에 대해 알아보도록 하겠습니다.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,